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 <string> 6 #include <vector> 7 8 #include "base/bind.h" 9 #include "base/file_util.h" 10 #include "base/files/file_path.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/format_macros.h" 13 #include "base/logging.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/message_loop/message_loop.h" 16 #include "base/run_loop.h" 17 #include "base/strings/stringprintf.h" 18 #include "chrome/browser/media_galleries/fileapi/itunes_data_provider.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/test/base/in_process_browser_test.h" 22 #include "content/public/browser/browser_thread.h" 23 #include "url/gurl.h" 24 25 using chrome::MediaFileSystemBackend; 26 27 namespace itunes { 28 29 namespace { 30 31 struct LibraryEntry { 32 LibraryEntry(const std::string& artist, const std::string& album, 33 const base::FilePath& location) 34 : artist(artist), 35 album(album), 36 location(location) { 37 } 38 std::string artist; 39 std::string album; 40 base::FilePath location; 41 }; 42 43 } // namespace 44 45 class TestITunesDataProvider : public ITunesDataProvider { 46 public: 47 TestITunesDataProvider(const base::FilePath& xml_library_path, 48 const base::Closure& callback) 49 : ITunesDataProvider(xml_library_path), 50 callback_(callback) { 51 } 52 virtual ~TestITunesDataProvider() {} 53 54 private: 55 virtual void OnLibraryChanged(const base::FilePath& path, 56 bool error) OVERRIDE { 57 ITunesDataProvider::OnLibraryChanged(path, error); 58 callback_.Run(); 59 } 60 61 base::Closure callback_; 62 63 DISALLOW_COPY_AND_ASSIGN(TestITunesDataProvider); 64 }; 65 66 class ITunesDataProviderTest : public InProcessBrowserTest { 67 public: 68 ITunesDataProviderTest() {} 69 virtual ~ITunesDataProviderTest() {} 70 71 protected: 72 virtual void SetUp() OVERRIDE { 73 ASSERT_TRUE(library_dir_.CreateUniqueTempDir()); 74 WriteLibraryInternal(SetUpLibrary()); 75 // The ImportedMediaGalleryRegistry is created on which ever thread calls 76 // GetInstance() first. It shouldn't matter what thread creates, however 77 // in practice it is always created on the UI thread, so this calls 78 // GetInstance here to mirror those real conditions. 79 chrome::ImportedMediaGalleryRegistry::GetInstance(); 80 InProcessBrowserTest::SetUp(); 81 } 82 83 void RunTest() { 84 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 85 base::RunLoop loop; 86 quit_closure_ = loop.QuitClosure(); 87 MediaFileSystemBackend::MediaTaskRunner()->PostTask( 88 FROM_HERE, 89 base::Bind(&ITunesDataProviderTest::StartTestOnMediaTaskRunner, 90 base::Unretained(this))); 91 loop.Run(); 92 } 93 94 void WriteLibrary(const std::vector<LibraryEntry>& entries, 95 const base::Closure& callback) { 96 SetLibraryChangeCallback(callback); 97 WriteLibraryInternal(entries); 98 } 99 100 void SetLibraryChangeCallback(const base::Closure& callback) { 101 EXPECT_TRUE(library_changed_callback_.is_null()); 102 library_changed_callback_ = callback; 103 } 104 105 ITunesDataProvider* data_provider() const { 106 return chrome::ImportedMediaGalleryRegistry::ITunesDataProvider(); 107 } 108 109 const base::FilePath& library_dir() const { 110 return library_dir_.path(); 111 } 112 113 base::FilePath XmlFile() const { 114 return library_dir_.path().AppendASCII("library.xml"); 115 } 116 117 void ExpectTrackLocation(const std::string& artist, const std::string& album, 118 const std::string& track_name) { 119 base::FilePath track = 120 library_dir().AppendASCII(track_name).NormalizePathSeparators(); 121 EXPECT_EQ(track.value(), 122 data_provider()->GetTrackLocation( 123 artist, album, track_name).NormalizePathSeparators().value()); 124 } 125 126 void ExpectNoTrack(const std::string& artist, const std::string& album, 127 const std::string& track_name) { 128 EXPECT_TRUE(data_provider()->GetTrackLocation( 129 artist, album, track_name).empty()) << track_name; 130 } 131 132 133 // Get the initial set of library entries, called by SetUp. If no entries 134 // are returned the xml file is not created. 135 virtual std::vector<LibraryEntry> SetUpLibrary() { 136 return std::vector<LibraryEntry>(); 137 } 138 139 // Start the test. The data provider is refreshed before calling StartTest 140 // and the result of the refresh is passed in. 141 virtual void StartTest(bool parse_success) = 0; 142 143 void TestDone() { 144 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 145 chrome::ImportedMediaGalleryRegistry* imported_registry = 146 chrome::ImportedMediaGalleryRegistry::GetInstance(); 147 imported_registry->itunes_data_provider_.reset(); 148 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 149 quit_closure_); 150 } 151 152 private: 153 void StartTestOnMediaTaskRunner() { 154 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 155 chrome::ImportedMediaGalleryRegistry* imported_registry = 156 chrome::ImportedMediaGalleryRegistry::GetInstance(); 157 imported_registry->itunes_data_provider_.reset( 158 new TestITunesDataProvider( 159 XmlFile(), 160 base::Bind(&ITunesDataProviderTest::OnLibraryChanged, 161 base::Unretained(this)))); 162 data_provider()->RefreshData(base::Bind(&ITunesDataProviderTest::StartTest, 163 base::Unretained(this))); 164 }; 165 166 void OnLibraryChanged() { 167 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 168 if (!library_changed_callback_.is_null()) { 169 library_changed_callback_.Run(); 170 library_changed_callback_.Reset(); 171 } 172 } 173 174 void WriteLibraryInternal(const std::vector<LibraryEntry>& entries) { 175 if (!entries.size()) 176 return; 177 std::string xml = "<plist><dict><key>Tracks</key><dict>\n"; 178 for (size_t i = 0; i < entries.size(); ++i) { 179 std::string seperator; 180 #if defined(OS_WIN) 181 seperator = "/"; 182 #endif 183 GURL location("file://localhost" + seperator + 184 entries[i].location.AsUTF8Unsafe()); 185 std::string entry_string = base::StringPrintf( 186 "<key>%" PRIuS "</key><dict>\n" 187 " <key>Track ID</key><integer>%" PRIuS "</integer>\n" 188 " <key>Location</key><string>%s</string>\n" 189 " <key>Artist</key><string>%s</string>\n" 190 " <key>Album</key><string>%s</string>\n" 191 "</dict>\n", 192 i + 1, i + 1, location.spec().c_str(), entries[i].artist.c_str(), 193 entries[i].album.c_str()); 194 xml += entry_string; 195 } 196 xml += "</dict></dict></plist>\n"; 197 ASSERT_EQ(static_cast<int>(xml.size()), 198 file_util::WriteFile(XmlFile(), xml.c_str(), xml.size())); 199 } 200 201 base::ScopedTempDir library_dir_; 202 203 base::Closure library_changed_callback_; 204 205 base::Closure quit_closure_; 206 207 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderTest); 208 }; 209 210 class ITunesDataProviderBasicTest : public ITunesDataProviderTest { 211 public: 212 ITunesDataProviderBasicTest() {} 213 virtual ~ITunesDataProviderBasicTest() {} 214 215 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 216 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 217 std::vector<LibraryEntry> entries; 218 entries.push_back(LibraryEntry("Artist", "Album", track)); 219 return entries; 220 } 221 222 virtual void StartTest(bool parse_success) OVERRIDE { 223 EXPECT_TRUE(parse_success); 224 225 // KnownArtist 226 EXPECT_TRUE(data_provider()->KnownArtist("Artist")); 227 EXPECT_FALSE(data_provider()->KnownArtist("Artist2")); 228 229 // KnownAlbum 230 EXPECT_TRUE(data_provider()->KnownAlbum("Artist", "Album")); 231 EXPECT_FALSE(data_provider()->KnownAlbum("Artist", "Album2")); 232 EXPECT_FALSE(data_provider()->KnownAlbum("Artist2", "Album")); 233 234 // GetTrackLocation 235 ExpectTrackLocation("Artist", "Album", "Track.mp3"); 236 ExpectNoTrack("Artist", "Album", "Track2.mp3"); 237 ExpectNoTrack("Artist", "Album2", "Track.mp3"); 238 ExpectNoTrack("Artist2", "Album", "Track.mp3"); 239 240 // GetArtistNames 241 std::set<ITunesDataProvider::ArtistName> artists = 242 data_provider()->GetArtistNames(); 243 ASSERT_EQ(1U, artists.size()); 244 EXPECT_EQ("Artist", *artists.begin()); 245 246 // GetAlbumNames 247 std::set<ITunesDataProvider::AlbumName> albums = 248 data_provider()->GetAlbumNames("Artist"); 249 ASSERT_EQ(1U, albums.size()); 250 EXPECT_EQ("Album", *albums.begin()); 251 252 albums = data_provider()->GetAlbumNames("Artist2"); 253 EXPECT_EQ(0U, albums.size()); 254 255 // GetAlbum 256 base::FilePath track = 257 library_dir().AppendASCII("Track.mp3").NormalizePathSeparators(); 258 ITunesDataProvider::Album album = 259 data_provider()->GetAlbum("Artist", "Album"); 260 ASSERT_EQ(1U, album.size()); 261 EXPECT_EQ(track.BaseName().AsUTF8Unsafe(), album.begin()->first); 262 EXPECT_EQ(track.value(), 263 album.begin()->second.NormalizePathSeparators().value()); 264 265 album = data_provider()->GetAlbum("Artist", "Album2"); 266 EXPECT_EQ(0U, album.size()); 267 268 album = data_provider()->GetAlbum("Artist2", "Album"); 269 EXPECT_EQ(0U, album.size()); 270 271 TestDone(); 272 } 273 274 private: 275 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderBasicTest); 276 }; 277 278 class ITunesDataProviderRefreshTest : public ITunesDataProviderTest { 279 public: 280 ITunesDataProviderRefreshTest() {} 281 virtual ~ITunesDataProviderRefreshTest() {} 282 283 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 284 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 285 std::vector<LibraryEntry> entries; 286 entries.push_back(LibraryEntry("Artist", "Album", track)); 287 return entries; 288 } 289 290 virtual void StartTest(bool parse_success) OVERRIDE { 291 EXPECT_TRUE(parse_success); 292 293 // Initial contents. 294 ExpectTrackLocation("Artist", "Album", "Track.mp3"); 295 ExpectNoTrack("Artist2", "Album2", "Track2.mp3"); 296 297 // New file. 298 base::FilePath track2 = library_dir().AppendASCII("Track2.mp3"); 299 std::vector<LibraryEntry> entries; 300 entries.push_back(LibraryEntry("Artist2", "Album2", track2)); 301 WriteLibrary(entries, 302 base::Bind(&ITunesDataProviderRefreshTest::CheckAfterWrite, 303 base::Unretained(this))); 304 } 305 306 void CheckAfterWrite() { 307 // Content the same. 308 ExpectTrackLocation("Artist", "Album", "Track.mp3"); 309 ExpectNoTrack("Artist2", "Album2", "Track2.mp3"); 310 311 data_provider()->RefreshData( 312 base::Bind(&ITunesDataProviderRefreshTest::CheckRefresh, 313 base::Unretained(this))); 314 } 315 316 void CheckRefresh(bool is_valid) { 317 EXPECT_TRUE(is_valid); 318 319 ExpectTrackLocation("Artist2", "Album2", "Track2.mp3"); 320 ExpectNoTrack("Artist", "Album", "Track.mp3"); 321 TestDone(); 322 } 323 324 private: 325 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderRefreshTest); 326 }; 327 328 class ITunesDataProviderInvalidTest : public ITunesDataProviderTest { 329 public: 330 ITunesDataProviderInvalidTest() {} 331 virtual ~ITunesDataProviderInvalidTest() {} 332 333 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 334 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 335 std::vector<LibraryEntry> entries; 336 entries.push_back(LibraryEntry("Artist", "Album", track)); 337 return entries; 338 } 339 340 virtual void StartTest(bool parse_success) OVERRIDE { 341 EXPECT_TRUE(parse_success); 342 343 SetLibraryChangeCallback( 344 base::Bind(&ITunesDataProvider::RefreshData, 345 base::Unretained(data_provider()), 346 base::Bind(&ITunesDataProviderInvalidTest::CheckInvalid, 347 base::Unretained(this)))); 348 ASSERT_EQ(1L, file_util::WriteFile(XmlFile(), " ", 1)); 349 } 350 351 void CheckInvalid(bool is_valid) { 352 EXPECT_FALSE(is_valid); 353 TestDone(); 354 } 355 356 private: 357 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderInvalidTest); 358 }; 359 360 class ITunesDataProviderUniqueNameTest : public ITunesDataProviderTest { 361 public: 362 ITunesDataProviderUniqueNameTest() {} 363 virtual ~ITunesDataProviderUniqueNameTest() {} 364 365 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 366 base::FilePath track = library_dir().AppendASCII("Track.mp3"); 367 std::vector<LibraryEntry> entries; 368 // Dupe album names should get uniquified with the track id, which in the 369 // test framework is the vector index. 370 entries.push_back(LibraryEntry("Artist", "Album", track)); 371 entries.push_back(LibraryEntry("Artist", "Album", track)); 372 entries.push_back(LibraryEntry("Artist", "Album2", track)); 373 return entries; 374 } 375 376 virtual void StartTest(bool parse_success) OVERRIDE { 377 EXPECT_TRUE(parse_success); 378 379 base::FilePath track = 380 library_dir().AppendASCII("Track.mp3").NormalizePathSeparators(); 381 EXPECT_EQ(track.value(), 382 data_provider()->GetTrackLocation( 383 "Artist", "Album", 384 "Track (1).mp3").NormalizePathSeparators().value()); 385 EXPECT_EQ(track.value(), 386 data_provider()->GetTrackLocation( 387 "Artist", "Album", 388 "Track (2).mp3").NormalizePathSeparators().value()); 389 EXPECT_EQ(track.value(), 390 data_provider()->GetTrackLocation( 391 "Artist", "Album2", 392 "Track.mp3").NormalizePathSeparators().value()); 393 394 TestDone(); 395 } 396 397 private: 398 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderUniqueNameTest); 399 }; 400 401 class ITunesDataProviderEscapeTest : public ITunesDataProviderTest { 402 // Albums and tracks that aren't the same, but become the same after 403 // replacing bad characters are not handled properly, but that case should 404 // never happen in practice. 405 public: 406 ITunesDataProviderEscapeTest() {} 407 virtual ~ITunesDataProviderEscapeTest() {} 408 409 virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE { 410 base::FilePath track = library_dir().AppendASCII("Track:1.mp3"); 411 std::vector<LibraryEntry> entries; 412 entries.push_back(LibraryEntry("Artist:/name", "Album:name/", track)); 413 entries.push_back(LibraryEntry("Artist/name", "Album:name", track)); 414 entries.push_back(LibraryEntry("Artist/name", "Album:name", track)); 415 return entries; 416 } 417 418 virtual void StartTest(bool parse_success) OVERRIDE { 419 EXPECT_TRUE(parse_success); 420 421 base::FilePath track = 422 library_dir().AppendASCII("Track:1.mp3").NormalizePathSeparators(); 423 EXPECT_EQ(track.value(), 424 data_provider()->GetTrackLocation( 425 "Artist__name", "Album_name_", 426 "Track_1.mp3").NormalizePathSeparators().value()); 427 EXPECT_EQ(track.value(), 428 data_provider()->GetTrackLocation( 429 "Artist_name", "Album_name", 430 "Track_1 (2).mp3").NormalizePathSeparators().value()); 431 EXPECT_EQ(track.value(), 432 data_provider()->GetTrackLocation( 433 "Artist_name", "Album_name", 434 "Track_1 (3).mp3").NormalizePathSeparators().value()); 435 436 TestDone(); 437 } 438 439 private: 440 DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderEscapeTest); 441 }; 442 443 IN_PROC_BROWSER_TEST_F(ITunesDataProviderBasicTest, BasicTest) { 444 RunTest(); 445 } 446 447 IN_PROC_BROWSER_TEST_F(ITunesDataProviderRefreshTest, RefreshTest) { 448 RunTest(); 449 } 450 451 IN_PROC_BROWSER_TEST_F(ITunesDataProviderInvalidTest, InvalidTest) { 452 RunTest(); 453 } 454 455 IN_PROC_BROWSER_TEST_F(ITunesDataProviderUniqueNameTest, UniqueNameTest) { 456 RunTest(); 457 } 458 459 IN_PROC_BROWSER_TEST_F(ITunesDataProviderEscapeTest, EscapeTest) { 460 RunTest(); 461 } 462 463 } // namespace itunes 464