Home | History | Annotate | Download | only in fileapi
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/media_galleries/fileapi/itunes_data_provider.h"
      6 
      7 #include <map>
      8 
      9 #include "base/bind.h"
     10 #include "base/callback.h"
     11 #include "base/format_macros.h"
     12 #include "base/location.h"
     13 #include "base/logging.h"
     14 #include "base/platform_file.h"
     15 #include "base/stl_util.h"
     16 #include "base/strings/string_util.h"
     17 #include "base/strings/stringprintf.h"
     18 #include "base/threading/thread_restrictions.h"
     19 #include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
     20 #include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
     21 #include "chrome/common/media_galleries/itunes_library.h"
     22 #include "content/public/browser/browser_thread.h"
     23 #include "third_party/icu/source/common/unicode/locid.h"
     24 #include "webkit/browser/fileapi/native_file_util.h"
     25 
     26 using chrome::MediaFileSystemBackend;
     27 
     28 namespace itunes {
     29 
     30 namespace {
     31 
     32 typedef base::Callback<void(scoped_ptr<base::FilePathWatcher> watcher)>
     33     FileWatchStartedCallback;
     34 
     35 // Colon and slash are not allowed in filenames, replace them with underscore.
     36 std::string EscapeBadCharacters(const std::string& input) {
     37   std::string result;
     38   ReplaceChars(input, ":/", "_", &result);
     39   return result;
     40 }
     41 
     42 ITunesDataProvider::Album MakeUniqueTrackNames(const parser::Album& album) {
     43   // TODO(vandebo): It would be nice to ensure that names returned from here
     44   // are stable, but aside from persisting every name returned, it's not
     45   // obvious how to do that (without including the track id in every name).
     46   typedef std::set<const parser::Track*> TrackRefs;
     47   typedef std::map<ITunesDataProvider::TrackName, TrackRefs> AlbumInfo;
     48 
     49   ITunesDataProvider::Album result;
     50   AlbumInfo duped_tracks;
     51 
     52   parser::Album::const_iterator album_it;
     53   for (album_it = album.begin(); album_it != album.end(); ++album_it) {
     54     const parser::Track& track = *album_it;
     55     std::string name =
     56         EscapeBadCharacters(track.location.BaseName().AsUTF8Unsafe());
     57     duped_tracks[name].insert(&track);
     58   }
     59 
     60   for (AlbumInfo::const_iterator name_it = duped_tracks.begin();
     61        name_it != duped_tracks.end();
     62        ++name_it) {
     63     const TrackRefs& track_refs = name_it->second;
     64     if (track_refs.size() == 1) {
     65       result[name_it->first] = (*track_refs.begin())->location;
     66     } else {
     67       for (TrackRefs::const_iterator track_it = track_refs.begin();
     68            track_it != track_refs.end();
     69            ++track_it) {
     70         base::FilePath track_file_name = (*track_it)->location.BaseName();
     71         std::string id =
     72             base::StringPrintf(" (%" PRId64 ")", (*track_it)->id);
     73         std::string uniquified_track_name =
     74             track_file_name.InsertBeforeExtensionASCII(id).AsUTF8Unsafe();
     75         std::string escaped_track_name =
     76             EscapeBadCharacters(uniquified_track_name);
     77         result[escaped_track_name] = (*track_it)->location;
     78       }
     79     }
     80   }
     81 
     82   return result;
     83 }
     84 
     85 // Bounces |path| and |error| to |callback| from the FILE thread to the media
     86 // task runner.
     87 void OnLibraryChanged(const base::FilePathWatcher::Callback& callback,
     88                       const base::FilePath& path,
     89                       bool error) {
     90   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
     91   MediaFileSystemBackend::MediaTaskRunner()->PostTask(
     92       FROM_HERE, base::Bind(callback, path, error));
     93 }
     94 
     95 // The watch has to be started on the FILE thread, and the callback called by
     96 // the FilePathWatcher also needs to run on the FILE thread.
     97 void StartLibraryWatchOnFileThread(
     98     const base::FilePath& library_path,
     99     const FileWatchStartedCallback& watch_started_callback,
    100     const base::FilePathWatcher::Callback& library_changed_callback) {
    101   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
    102   scoped_ptr<base::FilePathWatcher> watcher(new base::FilePathWatcher);
    103   bool success = watcher->Watch(
    104       library_path, false /*recursive*/,
    105       base::Bind(&OnLibraryChanged, library_changed_callback));
    106   if (!success)
    107     LOG(ERROR) << "Adding watch for " << library_path.value() << " failed";
    108   MediaFileSystemBackend::MediaTaskRunner()->PostTask(
    109       FROM_HERE,
    110       base::Bind(watch_started_callback, base::Passed(&watcher)));
    111 }
    112 
    113 // |result_path| is set if |locale_string| maps to a localized directory name
    114 // and it exists in the filesystem.
    115 bool CheckLocaleStringAutoAddPath(
    116     const base::FilePath& media_path,
    117     const std::map<std::string, std::string>& localized_dir_names,
    118     const std::string& locale_string,
    119     base::FilePath* result_path) {
    120   DCHECK(!media_path.empty());
    121   DCHECK(!localized_dir_names.empty());
    122   DCHECK(!locale_string.empty());
    123   DCHECK(result_path);
    124 
    125   std::map<std::string, std::string>::const_iterator it =
    126       localized_dir_names.find(locale_string);
    127   if (it == localized_dir_names.end())
    128     return false;
    129 
    130   base::FilePath localized_auto_add_path =
    131       media_path.Append(base::FilePath::FromUTF8Unsafe(it->second));
    132   if (!fileapi::NativeFileUtil::DirectoryExists(localized_auto_add_path))
    133     return false;
    134 
    135   *result_path = localized_auto_add_path;
    136   return true;
    137 }
    138 
    139 // This method is complex because Apple localizes the directory name in versions
    140 // of iTunes before 10.6.
    141 base::FilePath GetAutoAddPath(const base::FilePath& library_path) {
    142   const char kiTunesMediaDir[] = "iTunes Media";
    143   base::FilePath media_path =
    144       library_path.DirName().AppendASCII(kiTunesMediaDir);
    145 
    146   // Test 'universal' path first.
    147   base::FilePath universal_auto_add_path =
    148       media_path.AppendASCII("Automatically Add to iTunes.localized");
    149   if (fileapi::NativeFileUtil::DirectoryExists(universal_auto_add_path))
    150     return universal_auto_add_path;
    151 
    152   // Test user locale. Localized directory names encoded in UTF-8.
    153   std::map<std::string, std::string> localized_dir_names;
    154   localized_dir_names["nl"] = "Voeg automatisch toe aan iTunes";
    155   localized_dir_names["en"] = "Automatically Add to iTunes";
    156   localized_dir_names["fr"] = "Ajouter automatiquement \xC3\xA0 iTunes";
    157   localized_dir_names["de"] = "Automatisch zu iTunes hinzuf\xC3\xBCgen";
    158   localized_dir_names["it"] = "Aggiungi automaticamente a iTunes";
    159   localized_dir_names["ja"] = "iTunes \xE3\x81\xAB\xE8\x87\xAA\xE5\x8B\x95\xE7"
    160                               "\x9A\x84\xE3\x81\xAB\xE8\xBF\xBD\xE5\x8A\xA0";
    161   localized_dir_names["es"] = "A\xC3\xB1""adir autom\xC3\xA1ticamente a iTunes";
    162   localized_dir_names["da"] = "F\xC3\xB8j automatisk til iTunes";
    163   localized_dir_names["en-GB"] = "Automatically Add to iTunes";
    164   localized_dir_names["fi"] = "Lis\xC3\xA4\xC3\xA4 automaattisesti iTunesiin";
    165   localized_dir_names["ko"] = "iTunes\xEC\x97\x90 \xEC\x9E\x90\xEB\x8F\x99\xEC"
    166                               "\x9C\xBC\xEB\xA1\x9C \xEC\xB6\x94\xEA\xB0\x80";
    167   localized_dir_names["no"] = "Legg til automatisk i iTunes";
    168   localized_dir_names["pl"] = "Automatycznie dodaj do iTunes";
    169   localized_dir_names["pt"] = "Adicionar Automaticamente ao iTunes";
    170   localized_dir_names["pt-PT"] = "Adicionar ao iTunes automaticamente";
    171   localized_dir_names["ru"] = "\xD0\x90\xD0\xB2\xD1\x82\xD0\xBE\xD0\xBC\xD0\xB0"
    172                               "\xD1\x82\xD0\xB8\xD1\x87\xD0\xB5\xD1\x81\xD0\xBA"
    173                               "\xD0\xB8 \xD0\xB4\xD0\xBE\xD0\xB1\xD0\xB0\xD0"
    174                               "\xB2\xD0\xBB\xD1\x8F\xD1\x82\xD1\x8C \xD0\xB2"
    175                               "iTunes";
    176   localized_dir_names["sv"] = "L\xC3\xA4gg automatiskt till i iTunes";
    177   localized_dir_names["zh-CN"] = "\xE8\x87\xAA\xE5\x8A\xA8\xE6\xB7\xBB\xE5\x8A"
    178                                  "\xA0\xE5\x88\xB0 iTunes";
    179   localized_dir_names["zh-TW"] = "\xE8\x87\xAA\xE5\x8B\x95\xE5\x8A\xA0\xE5\x85"
    180                                  "\xA5 iTunes";
    181 
    182   const icu::Locale locale = icu::Locale::getDefault();
    183   const char* language = locale.getLanguage();
    184   const char* country = locale.getCountry();
    185 
    186   base::FilePath result_path;
    187   if (language != NULL && *language != '\0') {
    188     if (country != NULL && *country != '\0' &&
    189         CheckLocaleStringAutoAddPath(media_path, localized_dir_names,
    190                                      std::string(language) + "-" + country,
    191                                      &result_path)) {
    192       return result_path;
    193     }
    194 
    195     if (CheckLocaleStringAutoAddPath(media_path, localized_dir_names,
    196                                      language, &result_path)) {
    197       return result_path;
    198     }
    199   }
    200 
    201   // Fallback to trying English.
    202   if (CheckLocaleStringAutoAddPath(media_path, localized_dir_names,
    203                                    "en", &result_path)) {
    204     return result_path;
    205   }
    206 
    207   return base::FilePath();
    208 }
    209 
    210 }  // namespace
    211 
    212 ITunesDataProvider::ITunesDataProvider(const base::FilePath& library_path)
    213     : library_path_(library_path),
    214       auto_add_path_(GetAutoAddPath(library_path)),
    215       needs_refresh_(true),
    216       is_valid_(false) {
    217   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    218   DCHECK(!library_path_.empty());
    219 
    220   content::BrowserThread::PostTask(
    221       content::BrowserThread::FILE,
    222       FROM_HERE,
    223       base::Bind(StartLibraryWatchOnFileThread,
    224                  library_path_,
    225                  base::Bind(&ITunesDataProvider::OnLibraryWatchStartedCallback),
    226                  base::Bind(&ITunesDataProvider::OnLibraryChangedCallback)));
    227 }
    228 
    229 ITunesDataProvider::~ITunesDataProvider() {}
    230 
    231 // TODO(vandebo): add a file watch that resets |needs_refresh_| when the
    232 // file changes.
    233 void ITunesDataProvider::RefreshData(const ReadyCallback& ready_callback) {
    234   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    235   if (!needs_refresh_) {
    236     ready_callback.Run(is_valid_);
    237     return;
    238   }
    239 
    240   needs_refresh_ = false;
    241   xml_parser_ = new SafeITunesLibraryParser(
    242       library_path_,
    243       base::Bind(&ITunesDataProvider::OnLibraryParsedCallback, ready_callback));
    244   xml_parser_->Start();
    245 }
    246 
    247 const base::FilePath& ITunesDataProvider::library_path() const {
    248   return library_path_;
    249 }
    250 
    251 const base::FilePath& ITunesDataProvider::auto_add_path() const {
    252   return auto_add_path_;
    253 }
    254 
    255 bool ITunesDataProvider::KnownArtist(const ArtistName& artist) const {
    256   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    257   DCHECK(is_valid_);
    258   return ContainsKey(library_, artist);
    259 }
    260 
    261 bool ITunesDataProvider::KnownAlbum(const ArtistName& artist,
    262                                     const AlbumName& album) const {
    263   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    264   DCHECK(is_valid_);
    265   Library::const_iterator library_it = library_.find(artist);
    266   if (library_it == library_.end())
    267     return false;
    268   return ContainsKey(library_it->second, album);
    269 }
    270 
    271 base::FilePath ITunesDataProvider::GetTrackLocation(
    272     const ArtistName& artist, const AlbumName& album,
    273     const TrackName& track) const {
    274   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    275   DCHECK(is_valid_);
    276   Library::const_iterator library_it = library_.find(artist);
    277   if (library_it == library_.end())
    278     return base::FilePath();
    279 
    280   Artist::const_iterator artist_it = library_it->second.find(album);
    281   if (artist_it == library_it->second.end())
    282     return base::FilePath();
    283 
    284   Album::const_iterator album_it = artist_it->second.find(track);
    285   if (album_it == artist_it->second.end())
    286     return base::FilePath();
    287   return album_it->second;
    288 }
    289 
    290 std::set<ITunesDataProvider::ArtistName>
    291 ITunesDataProvider::GetArtistNames() const {
    292   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    293   DCHECK(is_valid_);
    294   std::set<ArtistName> result;
    295   Library::const_iterator it;
    296   for (it = library_.begin(); it != library_.end(); ++it) {
    297     result.insert(it->first);
    298   }
    299   return result;
    300 }
    301 
    302 std::set<ITunesDataProvider::AlbumName> ITunesDataProvider::GetAlbumNames(
    303     const ArtistName& artist) const {
    304   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    305   DCHECK(is_valid_);
    306   std::set<AlbumName> result;
    307   Library::const_iterator artist_lookup = library_.find(artist);
    308   if (artist_lookup == library_.end())
    309     return result;
    310 
    311   const Artist& artist_entry = artist_lookup->second;
    312   Artist::const_iterator it;
    313   for (it = artist_entry.begin(); it != artist_entry.end(); ++it) {
    314     result.insert(it->first);
    315   }
    316   return result;
    317 }
    318 
    319 ITunesDataProvider::Album ITunesDataProvider::GetAlbum(
    320     const ArtistName& artist, const AlbumName& album) const {
    321   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    322   DCHECK(is_valid_);
    323   Album result;
    324   Library::const_iterator artist_lookup = library_.find(artist);
    325   if (artist_lookup != library_.end()) {
    326     Artist::const_iterator album_lookup = artist_lookup->second.find(album);
    327     if (album_lookup != artist_lookup->second.end())
    328       result = album_lookup->second;
    329   }
    330   return result;
    331 }
    332 
    333 // static
    334 void ITunesDataProvider::OnLibraryWatchStartedCallback(
    335     scoped_ptr<base::FilePathWatcher> library_watcher) {
    336   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    337   ITunesDataProvider* provider =
    338       chrome::ImportedMediaGalleryRegistry::ITunesDataProvider();
    339   if (provider)
    340     provider->OnLibraryWatchStarted(library_watcher.Pass());
    341 }
    342 
    343 // static
    344 void ITunesDataProvider::OnLibraryChangedCallback(const base::FilePath& path,
    345                                                   bool error) {
    346   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    347   ITunesDataProvider* provider =
    348       chrome::ImportedMediaGalleryRegistry::ITunesDataProvider();
    349   if (provider)
    350     provider->OnLibraryChanged(path, error);
    351 }
    352 
    353 // static
    354 void ITunesDataProvider::OnLibraryParsedCallback(
    355     const ReadyCallback& ready_callback,
    356     bool result,
    357     const parser::Library& library) {
    358   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    359   ITunesDataProvider* provider =
    360       chrome::ImportedMediaGalleryRegistry::ITunesDataProvider();
    361   if (!provider) {
    362     ready_callback.Run(false);
    363     return;
    364   }
    365   provider->OnLibraryParsed(ready_callback, result, library);
    366 }
    367 
    368 void ITunesDataProvider::OnLibraryWatchStarted(
    369     scoped_ptr<base::FilePathWatcher> library_watcher) {
    370   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    371   library_watcher_.reset(library_watcher.release());
    372 }
    373 
    374 void ITunesDataProvider::OnLibraryChanged(const base::FilePath& path,
    375                                           bool error) {
    376   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    377   DCHECK_EQ(library_path_.value(), path.value());
    378   if (error)
    379     LOG(ERROR) << "Error watching " << library_path_.value();
    380   needs_refresh_ = true;
    381 }
    382 
    383 void ITunesDataProvider::OnLibraryParsed(const ReadyCallback& ready_callback,
    384                                          bool result,
    385                                          const parser::Library& library) {
    386   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    387   is_valid_ = result;
    388   if (is_valid_) {
    389     library_.clear();
    390     for (parser::Library::const_iterator artist_it = library.begin();
    391          artist_it != library.end();
    392          ++artist_it) {
    393       std::string artist_name = EscapeBadCharacters(artist_it->first);
    394       for (parser::Albums::const_iterator album_it = artist_it->second.begin();
    395            album_it != artist_it->second.end();
    396            ++album_it) {
    397         std::string album_name = EscapeBadCharacters(album_it->first);
    398         library_[artist_name][album_name] =
    399             MakeUniqueTrackNames(album_it->second);
    400       }
    401     }
    402   }
    403   ready_callback.Run(is_valid_);
    404 }
    405 
    406 }  // namespace itunes
    407