mime_util_xdg.cc revision 868fa2fe829687343ffae624259930155e16dbd8
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/nix/mime_util_xdg.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <cstdlib>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <list>
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <map>
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <vector>
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/environment.h"
1390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/file_util.h"
1490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/lazy_instance.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/scoped_ptr.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/singleton.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/nix/xdg_util.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/strings/string_split.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/strings/string_util.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/synchronization/lock.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/third_party/xdg_mime/xdgmime.h"
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/threading/thread_restrictions.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/time.h"
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)namespace base {
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace nix {
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class IconTheme;
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// None of the XDG stuff is thread-safe, so serialize all access under
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// this lock.
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)base::LazyInstance<base::Lock>::Leaky
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_mime_util_xdg_lock = LAZY_INSTANCE_INITIALIZER;
37116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
38116680a4aac90f2aa7413d9095a592090648e557Ben Murdochclass MimeUtilConstants {
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  typedef std::map<std::string, IconTheme*> IconThemeMap;
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  typedef std::map<FilePath, base::Time> IconDirMtimeMap;
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  typedef std::vector<std::string> IconFormats;
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Specified by XDG icon theme specs.
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  static const int kUpdateIntervalInSeconds = 5;
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  static const size_t kDefaultThemeNum = 4;
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  static MimeUtilConstants* GetInstance() {
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return Singleton<MimeUtilConstants>::get();
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Store icon directories and their mtimes.
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  IconDirMtimeMap icon_dirs_;
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Store icon formats.
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  IconFormats icon_formats_;
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Store loaded icon_theme.
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  IconThemeMap icon_themes_;
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The default theme.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  IconTheme* default_themes_[kDefaultThemeNum];
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::TimeTicks last_check_time_;
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The current icon theme, usually set through GTK theme integration.
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string icon_theme_name_;
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  MimeUtilConstants() {
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    icon_formats_.push_back(".png");
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    icon_formats_.push_back(".svg");
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    icon_formats_.push_back(".xpm");
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (size_t i = 0; i < kDefaultThemeNum; ++i)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default_themes_[i] = NULL;
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ~MimeUtilConstants();
80116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
81116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  friend struct DefaultSingletonTraits<MimeUtilConstants>;
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(MimeUtilConstants);
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
85868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// IconTheme represents an icon theme as defined by the xdg icon theme spec.
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Example themes on GNOME include 'Human' and 'Mist'.
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Example themes on KDE include 'crystalsvg' and 'kdeclassic'.
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class IconTheme {
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // A theme consists of multiple sub-directories, like '32x32' and 'scalable'.
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class SubDirInfo {
93   public:
94    // See spec for details.
95    enum Type {
96      Fixed,
97      Scalable,
98      Threshold
99    };
100    SubDirInfo()
101        : size(0),
102          type(Threshold),
103          max_size(0),
104          min_size(0),
105          threshold(2) {
106    }
107    size_t size;  // Nominal size of the icons in this directory.
108    Type type;  // Type of the icon size.
109    size_t max_size;  // Maximum size that the icons can be scaled to.
110    size_t min_size;  // Minimum size that the icons can be scaled to.
111    size_t threshold;  // Maximum difference from desired size. 2 by default.
112  };
113
114  explicit IconTheme(const std::string& name);
115
116  ~IconTheme() {}
117
118  // Returns the path to an icon with the name |icon_name| and a size of |size|
119  // pixels. If the icon does not exist, but |inherits| is true, then look for
120  // the icon in the parent theme.
121  FilePath GetIconPath(const std::string& icon_name, int size, bool inherits);
122
123  // Load a theme with the name |theme_name| into memory. Returns null if theme
124  // is invalid.
125  static IconTheme* LoadTheme(const std::string& theme_name);
126
127 private:
128  // Returns the path to an icon with the name |icon_name| in |subdir|.
129  FilePath GetIconPathUnderSubdir(const std::string& icon_name,
130                                  const std::string& subdir);
131
132  // Whether the theme loaded properly.
133  bool IsValid() {
134    return index_theme_loaded_;
135  }
136
137  // Read and parse |file| which is usually named 'index.theme' per theme spec.
138  bool LoadIndexTheme(const FilePath& file);
139
140  // Checks to see if the icons in |info| matches |size| (in pixels). Returns
141  // 0 if they match, or the size difference in pixels.
142  size_t MatchesSize(SubDirInfo* info, size_t size);
143
144  // Yet another function to read a line.
145  std::string ReadLine(FILE* fp);
146
147  // Set directories to search for icons to the comma-separated list |dirs|.
148  bool SetDirectories(const std::string& dirs);
149
150  bool index_theme_loaded_;  // True if an instance is properly loaded.
151  // store the scattered directories of this theme.
152  std::list<FilePath> dirs_;
153
154  // store the subdirs of this theme and array index of |info_array_|.
155  std::map<std::string, int> subdirs_;
156  scoped_ptr<SubDirInfo[]> info_array_;  // List of sub-directories.
157  std::string inherits_;  // Name of the theme this one inherits from.
158};
159
160IconTheme::IconTheme(const std::string& name)
161    : index_theme_loaded_(false) {
162  base::ThreadRestrictions::AssertIOAllowed();
163  // Iterate on all icon directories to find directories of the specified
164  // theme and load the first encountered index.theme.
165  MimeUtilConstants::IconDirMtimeMap::iterator iter;
166  FilePath theme_path;
167  MimeUtilConstants::IconDirMtimeMap* icon_dirs =
168      &MimeUtilConstants::GetInstance()->icon_dirs_;
169  for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) {
170    theme_path = iter->first.Append(name);
171    if (!file_util::DirectoryExists(theme_path))
172      continue;
173    FilePath theme_index = theme_path.Append("index.theme");
174    if (!index_theme_loaded_ && file_util::PathExists(theme_index)) {
175      if (!LoadIndexTheme(theme_index))
176        return;
177      index_theme_loaded_ = true;
178    }
179    dirs_.push_back(theme_path);
180  }
181}
182
183FilePath IconTheme::GetIconPath(const std::string& icon_name, int size,
184                                bool inherits) {
185  std::map<std::string, int>::iterator subdir_iter;
186  FilePath icon_path;
187
188  for (subdir_iter = subdirs_.begin();
189       subdir_iter != subdirs_.end();
190       ++subdir_iter) {
191    SubDirInfo* info = &info_array_[subdir_iter->second];
192    if (MatchesSize(info, size) == 0) {
193      icon_path = GetIconPathUnderSubdir(icon_name, subdir_iter->first);
194      if (!icon_path.empty())
195        return icon_path;
196    }
197  }
198  // Now looking for the mostly matched.
199  size_t min_delta_seen = 9999;
200
201  for (subdir_iter = subdirs_.begin();
202       subdir_iter != subdirs_.end();
203       ++subdir_iter) {
204    SubDirInfo* info = &info_array_[subdir_iter->second];
205    size_t delta = MatchesSize(info, size);
206    if (delta < min_delta_seen) {
207      FilePath path = GetIconPathUnderSubdir(icon_name, subdir_iter->first);
208      if (!path.empty()) {
209        min_delta_seen = delta;
210        icon_path = path;
211      }
212    }
213  }
214
215  if (!icon_path.empty() || !inherits || inherits_ == "")
216    return icon_path;
217
218  IconTheme* theme = LoadTheme(inherits_);
219  // Inheriting from itself means the theme is buggy but we shouldn't crash.
220  if (theme && theme != this)
221    return theme->GetIconPath(icon_name, size, inherits);
222  else
223    return FilePath();
224}
225
226IconTheme* IconTheme::LoadTheme(const std::string& theme_name) {
227  scoped_ptr<IconTheme> theme;
228  MimeUtilConstants::IconThemeMap* icon_themes =
229      &MimeUtilConstants::GetInstance()->icon_themes_;
230  if (icon_themes->find(theme_name) != icon_themes->end()) {
231    theme.reset((*icon_themes)[theme_name]);
232  } else {
233    theme.reset(new IconTheme(theme_name));
234    if (!theme->IsValid())
235      theme.reset();
236    (*icon_themes)[theme_name] = theme.get();
237  }
238  return theme.release();
239}
240
241FilePath IconTheme::GetIconPathUnderSubdir(const std::string& icon_name,
242                                           const std::string& subdir) {
243  FilePath icon_path;
244  std::list<FilePath>::iterator dir_iter;
245  MimeUtilConstants::IconFormats* icon_formats =
246      &MimeUtilConstants::GetInstance()->icon_formats_;
247  for (dir_iter = dirs_.begin(); dir_iter != dirs_.end(); ++dir_iter) {
248    for (size_t i = 0; i < icon_formats->size(); ++i) {
249      icon_path = dir_iter->Append(subdir);
250      icon_path = icon_path.Append(icon_name + (*icon_formats)[i]);
251      if (file_util::PathExists(icon_path))
252        return icon_path;
253    }
254  }
255  return FilePath();
256}
257
258bool IconTheme::LoadIndexTheme(const FilePath& file) {
259  FILE* fp = file_util::OpenFile(file, "r");
260  SubDirInfo* current_info = NULL;
261  if (!fp)
262    return false;
263
264  // Read entries.
265  while (!feof(fp) && !ferror(fp)) {
266    std::string buf = ReadLine(fp);
267    if (buf == "")
268      break;
269
270    std::string entry;
271    TrimWhitespaceASCII(buf, TRIM_ALL, &entry);
272    if (entry.length() == 0 || entry[0] == '#') {
273      // Blank line or Comment.
274      continue;
275    } else if (entry[0] == '[' && info_array_.get()) {
276      current_info = NULL;
277      std::string subdir = entry.substr(1, entry.length() - 2);
278      if (subdirs_.find(subdir) != subdirs_.end())
279        current_info = &info_array_[subdirs_[subdir]];
280    }
281
282    std::string key, value;
283    std::vector<std::string> r;
284    base::SplitStringDontTrim(entry, '=', &r);
285    if (r.size() < 2)
286      continue;
287
288    TrimWhitespaceASCII(r[0], TRIM_ALL, &key);
289    for (size_t i = 1; i < r.size(); i++)
290      value.append(r[i]);
291    TrimWhitespaceASCII(value, TRIM_ALL, &value);
292
293    if (current_info) {
294      if (key == "Size") {
295        current_info->size = atoi(value.c_str());
296      } else if (key == "Type") {
297        if (value == "Fixed")
298          current_info->type = SubDirInfo::Fixed;
299        else if (value == "Scalable")
300          current_info->type = SubDirInfo::Scalable;
301        else if (value == "Threshold")
302          current_info->type = SubDirInfo::Threshold;
303      } else if (key == "MaxSize") {
304        current_info->max_size = atoi(value.c_str());
305      } else if (key == "MinSize") {
306        current_info->min_size = atoi(value.c_str());
307      } else if (key == "Threshold") {
308        current_info->threshold = atoi(value.c_str());
309      }
310    } else {
311      if (key.compare("Directories") == 0 && !info_array_.get()) {
312        if (!SetDirectories(value)) break;
313      } else if (key.compare("Inherits") == 0) {
314        if (value != "hicolor")
315          inherits_ = value;
316      }
317    }
318  }
319
320  file_util::CloseFile(fp);
321  return info_array_.get() != NULL;
322}
323
324size_t IconTheme::MatchesSize(SubDirInfo* info, size_t size) {
325  if (info->type == SubDirInfo::Fixed) {
326    if (size > info->size)
327      return size - info->size;
328    else
329      return info->size - size;
330  } else if (info->type == SubDirInfo::Scalable) {
331    if (size < info->min_size)
332      return info->min_size - size;
333    if (size > info->max_size)
334      return size - info->max_size;
335    return 0;
336  } else {
337    if (size + info->threshold < info->size)
338      return info->size - size - info->threshold;
339    if (size > info->size + info->threshold)
340      return size - info->size - info->threshold;
341    return 0;
342  }
343}
344
345std::string IconTheme::ReadLine(FILE* fp) {
346  if (!fp)
347    return std::string();
348
349  std::string result;
350  const size_t kBufferSize = 100;
351  char buffer[kBufferSize];
352  while ((fgets(buffer, kBufferSize - 1, fp)) != NULL) {
353    result += buffer;
354    size_t len = result.length();
355    if (len == 0)
356      break;
357    char end = result[len - 1];
358    if (end == '\n' || end == '\0')
359      break;
360  }
361
362  return result;
363}
364
365bool IconTheme::SetDirectories(const std::string& dirs) {
366  int num = 0;
367  std::string::size_type pos = 0, epos;
368  std::string dir;
369  while ((epos = dirs.find(',', pos)) != std::string::npos) {
370    TrimWhitespaceASCII(dirs.substr(pos, epos - pos), TRIM_ALL, &dir);
371    if (dir.length() == 0) {
372      DLOG(WARNING) << "Invalid index.theme: blank subdir";
373      return false;
374    }
375    subdirs_[dir] = num++;
376    pos = epos + 1;
377  }
378  TrimWhitespaceASCII(dirs.substr(pos), TRIM_ALL, &dir);
379  if (dir.length() == 0) {
380    DLOG(WARNING) << "Invalid index.theme: blank subdir";
381    return false;
382  }
383  subdirs_[dir] = num++;
384  info_array_.reset(new SubDirInfo[num]);
385  return true;
386}
387
388bool CheckDirExistsAndGetMtime(const FilePath& dir,
389                               base::Time* last_modified) {
390  if (!file_util::DirectoryExists(dir))
391    return false;
392  base::PlatformFileInfo file_info;
393  if (!file_util::GetFileInfo(dir, &file_info))
394    return false;
395  *last_modified = file_info.last_modified;
396  return true;
397}
398
399// Make sure |dir| exists and add it to the list of icon directories.
400void TryAddIconDir(const FilePath& dir) {
401  base::Time last_modified;
402  if (!CheckDirExistsAndGetMtime(dir, &last_modified))
403    return;
404  MimeUtilConstants::GetInstance()->icon_dirs_[dir] = last_modified;
405}
406
407// For a xdg directory |dir|, add the appropriate icon sub-directories.
408void AddXDGDataDir(const FilePath& dir) {
409  if (!file_util::DirectoryExists(dir))
410    return;
411  TryAddIconDir(dir.Append("icons"));
412  TryAddIconDir(dir.Append("pixmaps"));
413}
414
415// Add all the xdg icon directories.
416void InitIconDir() {
417  FilePath home = file_util::GetHomeDir();
418  if (!home.empty()) {
419      FilePath legacy_data_dir(home);
420      legacy_data_dir = legacy_data_dir.AppendASCII(".icons");
421      if (file_util::DirectoryExists(legacy_data_dir))
422        TryAddIconDir(legacy_data_dir);
423  }
424  const char* env = getenv("XDG_DATA_HOME");
425  if (env) {
426    AddXDGDataDir(FilePath(env));
427  } else if (!home.empty()) {
428    FilePath local_data_dir(home);
429    local_data_dir = local_data_dir.AppendASCII(".local");
430    local_data_dir = local_data_dir.AppendASCII("share");
431    AddXDGDataDir(local_data_dir);
432  }
433
434  env = getenv("XDG_DATA_DIRS");
435  if (!env) {
436    AddXDGDataDir(FilePath("/usr/local/share"));
437    AddXDGDataDir(FilePath("/usr/share"));
438  } else {
439    std::string xdg_data_dirs = env;
440    std::string::size_type pos = 0, epos;
441    while ((epos = xdg_data_dirs.find(':', pos)) != std::string::npos) {
442      AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos, epos - pos)));
443      pos = epos + 1;
444    }
445    AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos)));
446  }
447}
448
449void EnsureUpdated() {
450  MimeUtilConstants* constants = MimeUtilConstants::GetInstance();
451  if (constants->last_check_time_.is_null()) {
452    constants->last_check_time_ = base::TimeTicks::Now();
453    InitIconDir();
454    return;
455  }
456
457  // Per xdg theme spec, we should check the icon directories every so often
458  // for newly added icons.
459  base::TimeDelta time_since_last_check =
460      base::TimeTicks::Now() - constants->last_check_time_;
461  if (time_since_last_check.InSeconds() > constants->kUpdateIntervalInSeconds) {
462    constants->last_check_time_ += time_since_last_check;
463
464    bool rescan_icon_dirs = false;
465    MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_;
466    MimeUtilConstants::IconDirMtimeMap::iterator iter;
467    for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) {
468      base::Time last_modified;
469      if (!CheckDirExistsAndGetMtime(iter->first, &last_modified) ||
470          last_modified != iter->second) {
471        rescan_icon_dirs = true;
472        break;
473      }
474    }
475
476    if (rescan_icon_dirs) {
477      constants->icon_dirs_.clear();
478      constants->icon_themes_.clear();
479      InitIconDir();
480    }
481  }
482}
483
484// Find a fallback icon if we cannot find it in the default theme.
485FilePath LookupFallbackIcon(const std::string& icon_name) {
486  MimeUtilConstants* constants = MimeUtilConstants::GetInstance();
487  MimeUtilConstants::IconDirMtimeMap::iterator iter;
488  MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_;
489  MimeUtilConstants::IconFormats* icon_formats = &constants->icon_formats_;
490  for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) {
491    for (size_t i = 0; i < icon_formats->size(); ++i) {
492      FilePath icon = iter->first.Append(icon_name + (*icon_formats)[i]);
493      if (file_util::PathExists(icon))
494        return icon;
495    }
496  }
497  return FilePath();
498}
499
500// Initialize the list of default themes.
501void InitDefaultThemes() {
502  IconTheme** default_themes =
503      MimeUtilConstants::GetInstance()->default_themes_;
504
505  scoped_ptr<base::Environment> env(base::Environment::Create());
506  base::nix::DesktopEnvironment desktop_env =
507      base::nix::GetDesktopEnvironment(env.get());
508  if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
509      desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE4) {
510    // KDE
511    std::string kde_default_theme;
512    std::string kde_fallback_theme;
513
514    // TODO(thestig): Figure out how to get the current icon theme on KDE.
515    // Setting stored in ~/.kde/share/config/kdeglobals under Icons -> Theme.
516    default_themes[0] = NULL;
517
518    // Try some reasonable defaults for KDE.
519    if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3) {
520      // KDE 3
521      kde_default_theme = "default.kde";
522      kde_fallback_theme = "crystalsvg";
523    } else {
524      // KDE 4
525      kde_default_theme = "default.kde4";
526      kde_fallback_theme = "oxygen";
527    }
528    default_themes[1] = IconTheme::LoadTheme(kde_default_theme);
529    default_themes[2] = IconTheme::LoadTheme(kde_fallback_theme);
530  } else {
531    // Assume it's Gnome and use GTK to figure out the theme.
532    default_themes[1] = IconTheme::LoadTheme(
533        MimeUtilConstants::GetInstance()->icon_theme_name_);
534    default_themes[2] = IconTheme::LoadTheme("gnome");
535  }
536  // hicolor needs to be last per icon theme spec.
537  default_themes[3] = IconTheme::LoadTheme("hicolor");
538
539  for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) {
540    if (default_themes[i] == NULL)
541      continue;
542    // NULL out duplicate pointers.
543    for (size_t j = i + 1; j < MimeUtilConstants::kDefaultThemeNum; j++) {
544      if (default_themes[j] == default_themes[i])
545        default_themes[j] = NULL;
546    }
547  }
548}
549
550// Try to find an icon with the name |icon_name| that's |size| pixels.
551FilePath LookupIconInDefaultTheme(const std::string& icon_name, int size) {
552  EnsureUpdated();
553  MimeUtilConstants* constants = MimeUtilConstants::GetInstance();
554  MimeUtilConstants::IconThemeMap* icon_themes = &constants->icon_themes_;
555  if (icon_themes->empty())
556    InitDefaultThemes();
557
558  FilePath icon_path;
559  IconTheme** default_themes = constants->default_themes_;
560  for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) {
561    if (default_themes[i]) {
562      icon_path = default_themes[i]->GetIconPath(icon_name, size, true);
563      if (!icon_path.empty())
564        return icon_path;
565    }
566  }
567  return LookupFallbackIcon(icon_name);
568}
569
570MimeUtilConstants::~MimeUtilConstants() {
571  for (size_t i = 0; i < kDefaultThemeNum; i++)
572    delete default_themes_[i];
573}
574
575}  // namespace
576
577std::string GetFileMimeType(const FilePath& filepath) {
578  if (filepath.empty())
579    return std::string();
580  base::ThreadRestrictions::AssertIOAllowed();
581  base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
582  return xdg_mime_get_mime_type_from_file_name(filepath.value().c_str());
583}
584
585std::string GetDataMimeType(const std::string& data) {
586  base::ThreadRestrictions::AssertIOAllowed();
587  base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
588  return xdg_mime_get_mime_type_for_data(data.data(), data.length(), NULL);
589}
590
591void SetIconThemeName(const std::string& name) {
592  // If the theme name is already loaded, do nothing. Chrome doesn't respond
593  // to changes in the system theme, so we never need to set this more than
594  // once.
595  if (!MimeUtilConstants::GetInstance()->icon_theme_name_.empty())
596    return;
597
598  MimeUtilConstants::GetInstance()->icon_theme_name_ = name;
599}
600
601FilePath GetMimeIcon(const std::string& mime_type, size_t size) {
602  base::ThreadRestrictions::AssertIOAllowed();
603  std::vector<std::string> icon_names;
604  std::string icon_name;
605  FilePath icon_file;
606
607  if (!mime_type.empty()) {
608    base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
609    const char *icon = xdg_mime_get_icon(mime_type.c_str());
610    icon_name = std::string(icon ? icon : "");
611  }
612
613  if (icon_name.length())
614    icon_names.push_back(icon_name);
615
616  // For text/plain, try text-plain.
617  icon_name = mime_type;
618  for (size_t i = icon_name.find('/', 0); i != std::string::npos;
619       i = icon_name.find('/', i + 1)) {
620    icon_name[i] = '-';
621  }
622  icon_names.push_back(icon_name);
623  // Also try gnome-mime-text-plain.
624  icon_names.push_back("gnome-mime-" + icon_name);
625
626  // Try "deb" for "application/x-deb" in KDE 3.
627  size_t x_substr_pos = mime_type.find("/x-");
628  if (x_substr_pos != std::string::npos) {
629    icon_name = mime_type.substr(x_substr_pos + 3);
630    icon_names.push_back(icon_name);
631  }
632
633  // Try generic name like text-x-generic.
634  icon_name = mime_type.substr(0, mime_type.find('/')) + "-x-generic";
635  icon_names.push_back(icon_name);
636
637  // Last resort
638  icon_names.push_back("unknown");
639
640  for (size_t i = 0; i < icon_names.size(); i++) {
641    if (icon_names[i][0] == '/') {
642      icon_file = FilePath(icon_names[i]);
643      if (file_util::PathExists(icon_file))
644        return icon_file;
645    } else {
646      icon_file = LookupIconInDefaultTheme(icon_names[i], size);
647      if (!icon_file.empty())
648        return icon_file;
649    }
650  }
651  return FilePath();
652}
653
654}  // namespace nix
655}  // namespace base
656