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/file_path_watcher_util.h"
     20 #include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
     21 #include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
     22 #include "chrome/common/media_galleries/itunes_library.h"
     23 #include "content/public/browser/browser_thread.h"
     24 #include "third_party/icu/source/common/unicode/locid.h"
     25 #include "webkit/browser/fileapi/native_file_util.h"
     26 
     27 namespace itunes {
     28 
     29 namespace {
     30 
     31 // Colon and slash are not allowed in filenames, replace them with underscore.
     32 std::string EscapeBadCharacters(const std::string& input) {
     33   std::string result;
     34   base::ReplaceChars(input, ":/", "_", &result);
     35   return result;
     36 }
     37 
     38 ITunesDataProvider::Album MakeUniqueTrackNames(const parser::Album& album) {
     39   // TODO(vandebo): It would be nice to ensure that names returned from here
     40   // are stable, but aside from persisting every name returned, it's not
     41   // obvious how to do that (without including the track id in every name).
     42   typedef std::set<const parser::Track*> TrackRefs;
     43   typedef std::map<ITunesDataProvider::TrackName, TrackRefs> AlbumInfo;
     44 
     45   ITunesDataProvider::Album result;
     46   AlbumInfo duped_tracks;
     47 
     48   parser::Album::const_iterator album_it;
     49   for (album_it = album.begin(); album_it != album.end(); ++album_it) {
     50     const parser::Track& track = *album_it;
     51     std::string name =
     52         EscapeBadCharacters(track.location.BaseName().AsUTF8Unsafe());
     53     duped_tracks[name].insert(&track);
     54   }
     55 
     56   for (AlbumInfo::const_iterator name_it = duped_tracks.begin();
     57        name_it != duped_tracks.end();
     58        ++name_it) {
     59     const TrackRefs& track_refs = name_it->second;
     60     if (track_refs.size() == 1) {
     61       result[name_it->first] = (*track_refs.begin())->location;
     62     } else {
     63       for (TrackRefs::const_iterator track_it = track_refs.begin();
     64            track_it != track_refs.end();
     65            ++track_it) {
     66         base::FilePath track_file_name = (*track_it)->location.BaseName();
     67         std::string id =
     68             base::StringPrintf(" (%" PRId64 ")", (*track_it)->id);
     69         std::string uniquified_track_name =
     70             track_file_name.InsertBeforeExtensionASCII(id).AsUTF8Unsafe();
     71         std::string escaped_track_name =
     72             EscapeBadCharacters(uniquified_track_name);
     73         result[escaped_track_name] = (*track_it)->location;
     74       }
     75     }
     76   }
     77 
     78   return result;
     79 }
     80 
     81 // |result_path| is set if |locale_string| maps to a localized directory name
     82 // and it exists in the filesystem.
     83 bool CheckLocaleStringAutoAddPath(
     84     const base::FilePath& media_path,
     85     const std::map<std::string, std::string>& localized_dir_names,
     86     const std::string& locale_string,
     87     base::FilePath* result_path) {
     88   DCHECK(!media_path.empty());
     89   DCHECK(!localized_dir_names.empty());
     90   DCHECK(!locale_string.empty());
     91   DCHECK(result_path);
     92 
     93   std::map<std::string, std::string>::const_iterator it =
     94       localized_dir_names.find(locale_string);
     95   if (it == localized_dir_names.end())
     96     return false;
     97 
     98   base::FilePath localized_auto_add_path =
     99       media_path.Append(base::FilePath::FromUTF8Unsafe(it->second));
    100   if (!fileapi::NativeFileUtil::DirectoryExists(localized_auto_add_path))
    101     return false;
    102 
    103   *result_path = localized_auto_add_path;
    104   return true;
    105 }
    106 
    107 // This method is complex because Apple localizes the directory name in versions
    108 // of iTunes before 10.6.
    109 base::FilePath GetAutoAddPath(const base::FilePath& library_path) {
    110   const char kiTunesMediaDir[] = "iTunes Media";
    111   base::FilePath media_path =
    112       library_path.DirName().AppendASCII(kiTunesMediaDir);
    113 
    114   // Test 'universal' path first.
    115   base::FilePath universal_auto_add_path =
    116       media_path.AppendASCII("Automatically Add to iTunes.localized");
    117   if (fileapi::NativeFileUtil::DirectoryExists(universal_auto_add_path))
    118     return universal_auto_add_path;
    119 
    120   // Test user locale. Localized directory names encoded in UTF-8.
    121   std::map<std::string, std::string> localized_dir_names;
    122   localized_dir_names["nl"] = "Voeg automatisch toe aan iTunes";
    123   localized_dir_names["en"] = "Automatically Add to iTunes";
    124   localized_dir_names["fr"] = "Ajouter automatiquement \xC3\xA0 iTunes";
    125   localized_dir_names["de"] = "Automatisch zu iTunes hinzuf\xC3\xBCgen";
    126   localized_dir_names["it"] = "Aggiungi automaticamente a iTunes";
    127   localized_dir_names["ja"] = "iTunes \xE3\x81\xAB\xE8\x87\xAA\xE5\x8B\x95\xE7"
    128                               "\x9A\x84\xE3\x81\xAB\xE8\xBF\xBD\xE5\x8A\xA0";
    129   localized_dir_names["es"] = "A\xC3\xB1""adir autom\xC3\xA1ticamente a iTunes";
    130   localized_dir_names["da"] = "F\xC3\xB8j automatisk til iTunes";
    131   localized_dir_names["en-GB"] = "Automatically Add to iTunes";
    132   localized_dir_names["fi"] = "Lis\xC3\xA4\xC3\xA4 automaattisesti iTunesiin";
    133   localized_dir_names["ko"] = "iTunes\xEC\x97\x90 \xEC\x9E\x90\xEB\x8F\x99\xEC"
    134                               "\x9C\xBC\xEB\xA1\x9C \xEC\xB6\x94\xEA\xB0\x80";
    135   localized_dir_names["no"] = "Legg til automatisk i iTunes";
    136   localized_dir_names["pl"] = "Automatycznie dodaj do iTunes";
    137   localized_dir_names["pt"] = "Adicionar Automaticamente ao iTunes";
    138   localized_dir_names["pt-PT"] = "Adicionar ao iTunes automaticamente";
    139   localized_dir_names["ru"] = "\xD0\x90\xD0\xB2\xD1\x82\xD0\xBE\xD0\xBC\xD0\xB0"
    140                               "\xD1\x82\xD0\xB8\xD1\x87\xD0\xB5\xD1\x81\xD0\xBA"
    141                               "\xD0\xB8 \xD0\xB4\xD0\xBE\xD0\xB1\xD0\xB0\xD0"
    142                               "\xB2\xD0\xBB\xD1\x8F\xD1\x82\xD1\x8C \xD0\xB2"
    143                               "iTunes";
    144   localized_dir_names["sv"] = "L\xC3\xA4gg automatiskt till i iTunes";
    145   localized_dir_names["zh-CN"] = "\xE8\x87\xAA\xE5\x8A\xA8\xE6\xB7\xBB\xE5\x8A"
    146                                  "\xA0\xE5\x88\xB0 iTunes";
    147   localized_dir_names["zh-TW"] = "\xE8\x87\xAA\xE5\x8B\x95\xE5\x8A\xA0\xE5\x85"
    148                                  "\xA5 iTunes";
    149 
    150   const icu::Locale locale = icu::Locale::getDefault();
    151   const char* language = locale.getLanguage();
    152   const char* country = locale.getCountry();
    153 
    154   base::FilePath result_path;
    155   if (language != NULL && *language != '\0') {
    156     if (country != NULL && *country != '\0' &&
    157         CheckLocaleStringAutoAddPath(media_path, localized_dir_names,
    158                                      std::string(language) + "-" + country,
    159                                      &result_path)) {
    160       return result_path;
    161     }
    162 
    163     if (CheckLocaleStringAutoAddPath(media_path, localized_dir_names,
    164                                      language, &result_path)) {
    165       return result_path;
    166     }
    167   }
    168 
    169   // Fallback to trying English.
    170   if (CheckLocaleStringAutoAddPath(media_path, localized_dir_names,
    171                                    "en", &result_path)) {
    172     return result_path;
    173   }
    174 
    175   return base::FilePath();
    176 }
    177 
    178 }  // namespace
    179 
    180 ITunesDataProvider::ITunesDataProvider(const base::FilePath& library_path)
    181     : iapps::IAppsDataProvider(library_path),
    182       auto_add_path_(GetAutoAddPath(library_path)),
    183       weak_factory_(this) {}
    184 
    185 ITunesDataProvider::~ITunesDataProvider() {}
    186 
    187 void ITunesDataProvider::DoParseLibrary(
    188     const base::FilePath& library_path,
    189     const ReadyCallback& ready_callback) {
    190   xml_parser_ = new iapps::SafeIAppsLibraryParser;
    191   xml_parser_->ParseITunesLibrary(
    192       library_path,
    193       base::Bind(&ITunesDataProvider::OnLibraryParsed,
    194                  weak_factory_.GetWeakPtr(),
    195                  ready_callback));
    196 }
    197 
    198 const base::FilePath& ITunesDataProvider::auto_add_path() const {
    199   return auto_add_path_;
    200 }
    201 
    202 bool ITunesDataProvider::KnownArtist(const ArtistName& artist) const {
    203   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    204   DCHECK(valid());
    205   return ContainsKey(library_, artist);
    206 }
    207 
    208 bool ITunesDataProvider::KnownAlbum(const ArtistName& artist,
    209                                     const AlbumName& album) const {
    210   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    211   DCHECK(valid());
    212   Library::const_iterator library_it = library_.find(artist);
    213   if (library_it == library_.end())
    214     return false;
    215   return ContainsKey(library_it->second, album);
    216 }
    217 
    218 base::FilePath ITunesDataProvider::GetTrackLocation(
    219     const ArtistName& artist, const AlbumName& album,
    220     const TrackName& track) const {
    221   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    222   DCHECK(valid());
    223   Library::const_iterator library_it = library_.find(artist);
    224   if (library_it == library_.end())
    225     return base::FilePath();
    226 
    227   Artist::const_iterator artist_it = library_it->second.find(album);
    228   if (artist_it == library_it->second.end())
    229     return base::FilePath();
    230 
    231   Album::const_iterator album_it = artist_it->second.find(track);
    232   if (album_it == artist_it->second.end())
    233     return base::FilePath();
    234   return album_it->second;
    235 }
    236 
    237 std::set<ITunesDataProvider::ArtistName>
    238 ITunesDataProvider::GetArtistNames() const {
    239   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    240   DCHECK(valid());
    241   std::set<ArtistName> result;
    242   Library::const_iterator it;
    243   for (it = library_.begin(); it != library_.end(); ++it) {
    244     result.insert(it->first);
    245   }
    246   return result;
    247 }
    248 
    249 std::set<ITunesDataProvider::AlbumName> ITunesDataProvider::GetAlbumNames(
    250     const ArtistName& artist) const {
    251   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    252   DCHECK(valid());
    253   std::set<AlbumName> result;
    254   Library::const_iterator artist_lookup = library_.find(artist);
    255   if (artist_lookup == library_.end())
    256     return result;
    257 
    258   const Artist& artist_entry = artist_lookup->second;
    259   Artist::const_iterator it;
    260   for (it = artist_entry.begin(); it != artist_entry.end(); ++it) {
    261     result.insert(it->first);
    262   }
    263   return result;
    264 }
    265 
    266 ITunesDataProvider::Album ITunesDataProvider::GetAlbum(
    267     const ArtistName& artist, const AlbumName& album) const {
    268   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    269   DCHECK(valid());
    270   Album result;
    271   Library::const_iterator artist_lookup = library_.find(artist);
    272   if (artist_lookup != library_.end()) {
    273     Artist::const_iterator album_lookup = artist_lookup->second.find(album);
    274     if (album_lookup != artist_lookup->second.end())
    275       result = album_lookup->second;
    276   }
    277   return result;
    278 }
    279 
    280 void ITunesDataProvider::OnLibraryParsed(const ReadyCallback& ready_callback,
    281                                          bool result,
    282                                          const parser::Library& library) {
    283   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
    284   set_valid(result);
    285   if (valid()) {
    286     library_.clear();
    287     for (parser::Library::const_iterator artist_it = library.begin();
    288          artist_it != library.end();
    289          ++artist_it) {
    290       std::string artist_name = EscapeBadCharacters(artist_it->first);
    291       for (parser::Albums::const_iterator album_it = artist_it->second.begin();
    292            album_it != artist_it->second.end();
    293            ++album_it) {
    294         std::string album_name = EscapeBadCharacters(album_it->first);
    295         library_[artist_name][album_name] =
    296             MakeUniqueTrackNames(album_it->second);
    297       }
    298     }
    299   }
    300   ready_callback.Run(valid());
    301 }
    302 
    303 }  // namespace itunes
    304