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_file_util.h" 6 7 #include <set> 8 #include <string> 9 #include <vector> 10 11 #include "base/bind_helpers.h" 12 #include "base/files/file_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "chrome/browser/media_galleries/fileapi/itunes_data_provider.h" 15 #include "chrome/browser/media_galleries/fileapi/media_path_filter.h" 16 #include "chrome/browser/media_galleries/imported_media_gallery_registry.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "storage/browser/fileapi/file_system_operation_context.h" 19 #include "storage/browser/fileapi/file_system_url.h" 20 #include "storage/browser/fileapi/native_file_util.h" 21 #include "storage/common/blob/shareable_file_reference.h" 22 #include "storage/common/fileapi/file_system_util.h" 23 24 using storage::DirectoryEntry; 25 26 namespace itunes { 27 28 namespace { 29 30 base::File::Error MakeDirectoryFileInfo(base::File::Info* file_info) { 31 base::File::Info result; 32 result.is_directory = true; 33 *file_info = result; 34 return base::File::FILE_OK; 35 } 36 37 std::vector<std::string> GetVirtualPathComponents( 38 const storage::FileSystemURL& url) { 39 ImportedMediaGalleryRegistry* imported_registry = 40 ImportedMediaGalleryRegistry::GetInstance(); 41 base::FilePath root = imported_registry->ImportedRoot().AppendASCII("itunes"); 42 43 DCHECK(root.IsParent(url.path()) || root == url.path()); 44 base::FilePath virtual_path; 45 root.AppendRelativePath(url.path(), &virtual_path); 46 47 std::vector<std::string> result; 48 storage::VirtualPath::GetComponentsUTF8Unsafe(virtual_path, &result); 49 return result; 50 } 51 52 } // namespace 53 54 const char kITunesLibraryXML[] = "iTunes Music Library.xml"; 55 const char kITunesMediaDir[] = "iTunes Media"; 56 const char kITunesMusicDir[] = "Music"; 57 const char kITunesAutoAddDir[] = "Automatically Add to iTunes"; 58 59 ITunesFileUtil::ITunesFileUtil(MediaPathFilter* media_path_filter) 60 : NativeMediaFileUtil(media_path_filter), 61 imported_registry_(NULL), 62 weak_factory_(this) { 63 } 64 65 ITunesFileUtil::~ITunesFileUtil() { 66 } 67 68 void ITunesFileUtil::GetFileInfoOnTaskRunnerThread( 69 scoped_ptr<storage::FileSystemOperationContext> context, 70 const storage::FileSystemURL& url, 71 const GetFileInfoCallback& callback) { 72 ITunesDataProvider* data_provider = GetDataProvider(); 73 // |data_provider| may be NULL if the file system was revoked before this 74 // operation had a chance to run. 75 if (!data_provider) { 76 GetFileInfoWithFreshDataProvider(context.Pass(), url, callback, false); 77 } else { 78 data_provider->RefreshData( 79 base::Bind(&ITunesFileUtil::GetFileInfoWithFreshDataProvider, 80 weak_factory_.GetWeakPtr(), base::Passed(&context), url, 81 callback)); 82 } 83 } 84 85 void ITunesFileUtil::ReadDirectoryOnTaskRunnerThread( 86 scoped_ptr<storage::FileSystemOperationContext> context, 87 const storage::FileSystemURL& url, 88 const ReadDirectoryCallback& callback) { 89 ITunesDataProvider* data_provider = GetDataProvider(); 90 // |data_provider| may be NULL if the file system was revoked before this 91 // operation had a chance to run. 92 if (!data_provider) { 93 ReadDirectoryWithFreshDataProvider(context.Pass(), url, callback, false); 94 } else { 95 data_provider->RefreshData( 96 base::Bind(&ITunesFileUtil::ReadDirectoryWithFreshDataProvider, 97 weak_factory_.GetWeakPtr(), base::Passed(&context), url, 98 callback)); 99 } 100 } 101 102 void ITunesFileUtil::CreateSnapshotFileOnTaskRunnerThread( 103 scoped_ptr<storage::FileSystemOperationContext> context, 104 const storage::FileSystemURL& url, 105 const CreateSnapshotFileCallback& callback) { 106 ITunesDataProvider* data_provider = GetDataProvider(); 107 // |data_provider| may be NULL if the file system was revoked before this 108 // operation had a chance to run. 109 if (!data_provider) { 110 CreateSnapshotFileWithFreshDataProvider(context.Pass(), url, callback, 111 false); 112 } else { 113 data_provider->RefreshData( 114 base::Bind(&ITunesFileUtil::CreateSnapshotFileWithFreshDataProvider, 115 weak_factory_.GetWeakPtr(), base::Passed(&context), url, 116 callback)); 117 } 118 } 119 120 // Contents of the iTunes media gallery: 121 // / - root directory 122 // /iTunes Music Library.xml - library xml file 123 // /iTunes Media/Automatically Add to iTunes - auto-import directory 124 // /iTunes Media/Music/<Artist>/<Album>/<Track> - tracks 125 // 126 base::File::Error ITunesFileUtil::GetFileInfoSync( 127 storage::FileSystemOperationContext* context, 128 const storage::FileSystemURL& url, 129 base::File::Info* file_info, 130 base::FilePath* platform_path) { 131 std::vector<std::string> components = GetVirtualPathComponents(url); 132 133 if (components.size() == 0) 134 return MakeDirectoryFileInfo(file_info); 135 136 if (components.size() == 1 && components[0] == kITunesLibraryXML) { 137 // We can't just call NativeMediaFileUtil::GetFileInfoSync() here because it 138 // uses the MediaPathFilter. At this point, |library_path_| is known good 139 // because GetFileInfoWithFreshDataProvider() gates access to this method. 140 base::FilePath file_path = GetDataProvider()->library_path(); 141 if (platform_path) 142 *platform_path = file_path; 143 return storage::NativeFileUtil::GetFileInfo(file_path, file_info); 144 } 145 146 if (components[0] != kITunesMediaDir) 147 return base::File::FILE_ERROR_NOT_FOUND; 148 149 if (components[1] == kITunesAutoAddDir) { 150 if (GetDataProvider()->auto_add_path().empty()) 151 return base::File::FILE_ERROR_NOT_FOUND; 152 return NativeMediaFileUtil::GetFileInfoSync(context, url, file_info, 153 platform_path); 154 } 155 156 if (components[1] == kITunesMusicDir) { 157 switch (components.size()) { 158 case 2: 159 return MakeDirectoryFileInfo(file_info); 160 161 case 3: 162 if (GetDataProvider()->KnownArtist(components[2])) 163 return MakeDirectoryFileInfo(file_info); 164 break; 165 166 case 4: 167 if (GetDataProvider()->KnownAlbum(components[2], components[3])) 168 return MakeDirectoryFileInfo(file_info); 169 break; 170 171 case 5: { 172 base::FilePath location = 173 GetDataProvider()->GetTrackLocation(components[2], components[3], 174 components[4]); 175 if (!location.empty()) { 176 return NativeMediaFileUtil::GetFileInfoSync(context, url, file_info, 177 platform_path); 178 } 179 break; 180 } 181 } 182 } 183 184 return base::File::FILE_ERROR_NOT_FOUND; 185 } 186 187 base::File::Error ITunesFileUtil::ReadDirectorySync( 188 storage::FileSystemOperationContext* context, 189 const storage::FileSystemURL& url, 190 EntryList* file_list) { 191 DCHECK(file_list->empty()); 192 std::vector<std::string> components = GetVirtualPathComponents(url); 193 194 if (components.size() == 0) { 195 base::File::Info xml_info; 196 if (!base::GetFileInfo(GetDataProvider()->library_path(), &xml_info)) 197 return base::File::FILE_ERROR_IO; 198 file_list->push_back(DirectoryEntry(kITunesLibraryXML, 199 DirectoryEntry::FILE, 200 xml_info.size, xml_info.last_modified)); 201 file_list->push_back(DirectoryEntry(kITunesMediaDir, 202 DirectoryEntry::DIRECTORY, 203 0, base::Time())); 204 return base::File::FILE_OK; 205 } 206 207 if (components.size() == 1 && components[0] == kITunesLibraryXML) 208 return base::File::FILE_ERROR_NOT_A_DIRECTORY; 209 210 if (components[0] != kITunesMediaDir || components.size() > 5) 211 return base::File::FILE_ERROR_NOT_FOUND; 212 213 if (components.size() == 1) { 214 if (!GetDataProvider()->auto_add_path().empty()) { 215 file_list->push_back(DirectoryEntry(kITunesAutoAddDir, 216 DirectoryEntry::DIRECTORY, 217 0, base::Time())); 218 } 219 file_list->push_back(DirectoryEntry(kITunesMusicDir, 220 DirectoryEntry::DIRECTORY, 221 0, base::Time())); 222 return base::File::FILE_OK; 223 } 224 225 if (components[1] == kITunesAutoAddDir && 226 !GetDataProvider()->auto_add_path().empty()) { 227 return NativeMediaFileUtil::ReadDirectorySync(context, url, file_list); 228 } 229 230 if (components[1] != kITunesMusicDir) 231 return base::File::FILE_ERROR_NOT_FOUND; 232 233 if (components.size() == 2) { 234 std::set<ITunesDataProvider::ArtistName> artists = 235 GetDataProvider()->GetArtistNames(); 236 std::set<ITunesDataProvider::ArtistName>::const_iterator it; 237 for (it = artists.begin(); it != artists.end(); ++it) 238 file_list->push_back(DirectoryEntry(*it, DirectoryEntry::DIRECTORY, 239 0, base::Time())); 240 return base::File::FILE_OK; 241 } 242 243 if (components.size() == 3) { 244 std::set<ITunesDataProvider::AlbumName> albums = 245 GetDataProvider()->GetAlbumNames(components[2]); 246 if (albums.size() == 0) 247 return base::File::FILE_ERROR_NOT_FOUND; 248 std::set<ITunesDataProvider::AlbumName>::const_iterator it; 249 for (it = albums.begin(); it != albums.end(); ++it) 250 file_list->push_back(DirectoryEntry(*it, DirectoryEntry::DIRECTORY, 251 0, base::Time())); 252 return base::File::FILE_OK; 253 } 254 255 if (components.size() == 4) { 256 ITunesDataProvider::Album album = 257 GetDataProvider()->GetAlbum(components[2], components[3]); 258 if (album.size() == 0) 259 return base::File::FILE_ERROR_NOT_FOUND; 260 ITunesDataProvider::Album::const_iterator it; 261 for (it = album.begin(); it != album.end(); ++it) { 262 base::File::Info file_info; 263 if (media_path_filter()->Match(it->second) && 264 base::GetFileInfo(it->second, &file_info)) { 265 file_list->push_back(DirectoryEntry(it->first, DirectoryEntry::FILE, 266 file_info.size, 267 file_info.last_modified)); 268 } 269 } 270 return base::File::FILE_OK; 271 } 272 273 // At this point, the only choice is one of two errors, but figuring out 274 // which one is required. 275 DCHECK_EQ(4UL, components.size()); 276 base::FilePath location; 277 location = GetDataProvider()->GetTrackLocation(components[1], components[2], 278 components[3]); 279 if (!location.empty()) 280 return base::File::FILE_ERROR_NOT_A_DIRECTORY; 281 return base::File::FILE_ERROR_NOT_FOUND; 282 } 283 284 base::File::Error ITunesFileUtil::DeleteDirectorySync( 285 storage::FileSystemOperationContext* context, 286 const storage::FileSystemURL& url) { 287 return base::File::FILE_ERROR_SECURITY; 288 } 289 290 base::File::Error ITunesFileUtil::DeleteFileSync( 291 storage::FileSystemOperationContext* context, 292 const storage::FileSystemURL& url) { 293 return base::File::FILE_ERROR_SECURITY; 294 } 295 296 base::File::Error ITunesFileUtil::CreateSnapshotFileSync( 297 storage::FileSystemOperationContext* context, 298 const storage::FileSystemURL& url, 299 base::File::Info* file_info, 300 base::FilePath* platform_path, 301 scoped_refptr<storage::ShareableFileReference>* file_ref) { 302 std::vector<std::string> components = GetVirtualPathComponents(url); 303 if (components.size() != 1 || components[0] != kITunesLibraryXML) { 304 return NativeMediaFileUtil::CreateSnapshotFileSync(context, url, file_info, 305 platform_path, file_ref); 306 } 307 308 // The following code is different than 309 // NativeMediaFileUtil::CreateSnapshotFileSync in that it knows that the 310 // library xml file is not a directory and it doesn't run mime sniffing on the 311 // file. The only way to get here is by way of 312 // CreateSnapshotFileWithFreshDataProvider() so the file has already been 313 // parsed and deemed valid. 314 *file_ref = scoped_refptr<storage::ShareableFileReference>(); 315 return GetFileInfoSync(context, url, file_info, platform_path); 316 } 317 318 base::File::Error ITunesFileUtil::GetLocalFilePath( 319 storage::FileSystemOperationContext* context, 320 const storage::FileSystemURL& url, 321 base::FilePath* local_file_path) { 322 std::vector<std::string> components = GetVirtualPathComponents(url); 323 324 if (components.size() == 1 && components[0] == kITunesLibraryXML) { 325 *local_file_path = GetDataProvider()->library_path(); 326 return base::File::FILE_OK; 327 } 328 329 if (components.size() >= 2 && components[0] == kITunesMediaDir && 330 components[1] == kITunesAutoAddDir) { 331 *local_file_path = GetDataProvider()->auto_add_path(); 332 if (local_file_path->empty()) 333 return base::File::FILE_ERROR_NOT_FOUND; 334 335 for (size_t i = 2; i < components.size(); ++i) { 336 *local_file_path = local_file_path->Append( 337 base::FilePath::FromUTF8Unsafe(components[i])); 338 } 339 return base::File::FILE_OK; 340 } 341 342 // Should only get here for files, i.e. the xml file and tracks. 343 if (components[0] != kITunesMediaDir || components[1] != kITunesMusicDir|| 344 components.size() != 5) { 345 return base::File::FILE_ERROR_NOT_FOUND; 346 } 347 348 *local_file_path = GetDataProvider()->GetTrackLocation(components[2], 349 components[3], 350 components[4]); 351 if (!local_file_path->empty()) 352 return base::File::FILE_OK; 353 354 return base::File::FILE_ERROR_NOT_FOUND; 355 } 356 357 void ITunesFileUtil::GetFileInfoWithFreshDataProvider( 358 scoped_ptr<storage::FileSystemOperationContext> context, 359 const storage::FileSystemURL& url, 360 const GetFileInfoCallback& callback, 361 bool valid_parse) { 362 if (!valid_parse) { 363 if (!callback.is_null()) { 364 content::BrowserThread::PostTask( 365 content::BrowserThread::IO, 366 FROM_HERE, 367 base::Bind(callback, base::File::FILE_ERROR_IO, 368 base::File::Info())); 369 } 370 return; 371 } 372 NativeMediaFileUtil::GetFileInfoOnTaskRunnerThread(context.Pass(), url, 373 callback); 374 } 375 376 void ITunesFileUtil::ReadDirectoryWithFreshDataProvider( 377 scoped_ptr<storage::FileSystemOperationContext> context, 378 const storage::FileSystemURL& url, 379 const ReadDirectoryCallback& callback, 380 bool valid_parse) { 381 if (!valid_parse) { 382 if (!callback.is_null()) { 383 content::BrowserThread::PostTask( 384 content::BrowserThread::IO, 385 FROM_HERE, 386 base::Bind(callback, base::File::FILE_ERROR_IO, EntryList(), false)); 387 } 388 return; 389 } 390 NativeMediaFileUtil::ReadDirectoryOnTaskRunnerThread(context.Pass(), url, 391 callback); 392 } 393 394 void ITunesFileUtil::CreateSnapshotFileWithFreshDataProvider( 395 scoped_ptr<storage::FileSystemOperationContext> context, 396 const storage::FileSystemURL& url, 397 const CreateSnapshotFileCallback& callback, 398 bool valid_parse) { 399 if (!valid_parse) { 400 if (!callback.is_null()) { 401 base::File::Info file_info; 402 base::FilePath platform_path; 403 scoped_refptr<storage::ShareableFileReference> file_ref; 404 content::BrowserThread::PostTask( 405 content::BrowserThread::IO, 406 FROM_HERE, 407 base::Bind(callback, base::File::FILE_ERROR_IO, file_info, 408 platform_path, file_ref)); 409 } 410 return; 411 } 412 NativeMediaFileUtil::CreateSnapshotFileOnTaskRunnerThread(context.Pass(), url, 413 callback); 414 } 415 416 ITunesDataProvider* ITunesFileUtil::GetDataProvider() { 417 if (!imported_registry_) 418 imported_registry_ = ImportedMediaGalleryRegistry::GetInstance(); 419 return imported_registry_->ITunesDataProvider(); 420 } 421 422 } // namespace itunes 423