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