• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "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.
SanitizeName(const std::string & input)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 
MakeUniqueTrackNames(const parser::Album & album)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.
CheckLocaleStringAutoAddPath(const base::FilePath & media_path,const std::map<std::string,std::string> & localized_dir_names,const std::string & locale_string,base::FilePath * result_path)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 (!fileapi::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.
GetAutoAddPath(const base::FilePath & library_path)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 (fileapi::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 
ITunesDataProvider(const base::FilePath & library_path)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 
~ITunesDataProvider()184 ITunesDataProvider::~ITunesDataProvider() {}
185 
DoParseLibrary(const base::FilePath & library_path,const ReadyCallback & ready_callback)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 
auto_add_path() const197 const base::FilePath& ITunesDataProvider::auto_add_path() const {
198   return auto_add_path_;
199 }
200 
KnownArtist(const ArtistName & artist) const201 bool ITunesDataProvider::KnownArtist(const ArtistName& artist) const {
202   DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
203   DCHECK(valid());
204   return ContainsKey(library_, artist);
205 }
206 
KnownAlbum(const ArtistName & artist,const AlbumName & album) const207 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 
GetTrackLocation(const ArtistName & artist,const AlbumName & album,const TrackName & track) const217 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>
GetArtistNames() const237 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 
GetAlbumNames(const ArtistName & artist) const248 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 
GetAlbum(const ArtistName & artist,const AlbumName & album) const265 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 
OnLibraryParsed(const ReadyCallback & ready_callback,bool result,const parser::Library & library)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