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