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/files/file_path.h" 10 #include "base/files/file_util.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/stl_util.h" 18 #include "base/strings/stringprintf.h" 19 #include "chrome/browser/media_galleries/fileapi/iphoto_data_provider.h" 20 #include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" 21 #include "chrome/browser/media_galleries/imported_media_gallery_registry.h" 22 #include "chrome/test/base/in_process_browser_test.h" 23 #include "content/public/browser/browser_thread.h" 24 #include "url/gurl.h" 25 26 using base::FilePath; 27 28 namespace iphoto { 29 30 class TestIPhotoDataProvider : public IPhotoDataProvider { 31 public: 32 TestIPhotoDataProvider(const base::FilePath& xml_library_path, 33 const base::Closure& callback) 34 : IPhotoDataProvider(xml_library_path), 35 callback_(callback) { 36 } 37 virtual ~TestIPhotoDataProvider() {} 38 39 private: 40 virtual void OnLibraryChanged(const base::FilePath& path, 41 bool error) OVERRIDE { 42 IPhotoDataProvider::OnLibraryChanged(path, error); 43 callback_.Run(); 44 } 45 46 base::Closure callback_; 47 48 DISALLOW_COPY_AND_ASSIGN(TestIPhotoDataProvider); 49 }; 50 51 class IPhotoDataProviderTest : public InProcessBrowserTest { 52 public: 53 IPhotoDataProviderTest() {} 54 virtual ~IPhotoDataProviderTest() {} 55 56 protected: 57 virtual void SetUp() OVERRIDE { 58 ASSERT_TRUE(library_dir_.CreateUniqueTempDir()); 59 WriteLibraryInternal(); 60 // The ImportedMediaGalleryRegistry is created on which ever thread calls 61 // GetInstance() first. It shouldn't matter what thread creates, however 62 // in practice it is always created on the UI thread, so this calls 63 // GetInstance here to mirror those real conditions. 64 ImportedMediaGalleryRegistry::GetInstance(); 65 InProcessBrowserTest::SetUp(); 66 } 67 68 void RunTest() { 69 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 70 base::RunLoop loop; 71 quit_closure_ = loop.QuitClosure(); 72 MediaFileSystemBackend::MediaTaskRunner()->PostTask( 73 FROM_HERE, 74 base::Bind(&IPhotoDataProviderTest::StartTestOnMediaTaskRunner, 75 base::Unretained(this))); 76 loop.Run(); 77 } 78 79 void WriteLibrary(const base::Closure& callback) { 80 SetLibraryChangeCallback(callback); 81 WriteLibraryInternal(); 82 } 83 84 void SetLibraryChangeCallback(const base::Closure& callback) { 85 EXPECT_TRUE(library_changed_callback_.is_null()); 86 library_changed_callback_ = callback; 87 } 88 89 IPhotoDataProvider* data_provider() const { 90 return ImportedMediaGalleryRegistry::IPhotoDataProvider(); 91 } 92 93 const base::FilePath& library_dir() const { 94 return library_dir_.path(); 95 } 96 97 base::FilePath XmlFile() const { 98 return library_dir_.path().AppendASCII("library.xml"); 99 } 100 101 // Start the test. The data provider is refreshed before calling StartTest 102 // and the result of the refresh is passed in. 103 virtual void StartTest(bool parse_success) = 0; 104 105 void TestDone() { 106 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 107 ImportedMediaGalleryRegistry* imported_registry = 108 ImportedMediaGalleryRegistry::GetInstance(); 109 imported_registry->iphoto_data_provider_.reset(); 110 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 111 quit_closure_); 112 } 113 114 // Override to provide a full library string. 115 virtual std::string GetLibraryString() { 116 return "<plist><dict>\n</dict></plist>\n"; 117 } 118 119 private: 120 void StartTestOnMediaTaskRunner() { 121 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 122 ImportedMediaGalleryRegistry* imported_registry = 123 ImportedMediaGalleryRegistry::GetInstance(); 124 imported_registry->iphoto_data_provider_.reset( 125 new TestIPhotoDataProvider( 126 XmlFile(), 127 base::Bind(&IPhotoDataProviderTest::OnLibraryChanged, 128 base::Unretained(this)))); 129 data_provider()->RefreshData(base::Bind(&IPhotoDataProviderTest::StartTest, 130 base::Unretained(this))); 131 }; 132 133 void OnLibraryChanged() { 134 DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread()); 135 if (!library_changed_callback_.is_null()) { 136 library_changed_callback_.Run(); 137 library_changed_callback_.Reset(); 138 } 139 } 140 141 void WriteLibraryInternal() { 142 std::string xml = GetLibraryString(); 143 ASSERT_EQ(static_cast<int>(xml.size()), 144 base::WriteFile(XmlFile(), xml.c_str(), xml.size())); 145 } 146 147 base::ScopedTempDir library_dir_; 148 149 base::Closure library_changed_callback_; 150 151 base::Closure quit_closure_; 152 153 DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderTest); 154 }; 155 156 class IPhotoDataProviderBasicTest : public IPhotoDataProviderTest { 157 public: 158 IPhotoDataProviderBasicTest() {} 159 virtual ~IPhotoDataProviderBasicTest() {} 160 161 virtual std::string GetLibraryString() OVERRIDE { 162 return "<plist><dict>\n" 163 "<key>List of Albums</key>\n" 164 "<array>" 165 " <dict>\n" 166 " <key>AlbumId</key>" 167 " <integer>14</integer>" 168 " <key>AlbumName</key>" 169 " <string>Album1</string>" 170 " <key>KeyList</key>" 171 " <array>" 172 " <string>1</string>" 173 " <string>3</string>" // [3] and [4] are name dupes 174 " <string>4</string>" 175 " </array>" 176 " </dict>\n" 177 " <dict>\n" 178 " <key>AlbumId</key>" 179 " <integer>15</integer>" 180 " <key>AlbumName</key>" 181 " <string>Album2</string>" 182 " <key>KeyList</key>" 183 " <array>" 184 " <string>2</string>" 185 " </array>" 186 " </dict>\n" 187 " <dict>\n" 188 " <key>AlbumId</key>" 189 " <integer>16</integer>" 190 " <key>AlbumName</key>" 191 " <string>Album5</string>" 192 " <key>KeyList</key>" 193 " <array>" 194 " <string>5</string>" // A name dupe of [2], but in another album. 195 " </array>" 196 " </dict>\n" 197 "</array>\n" 198 "<key>Master Image List</key>\n" 199 "<dict>\n" 200 " <key>1</key>\n" 201 " <dict>\n" 202 " <key>MediaType</key>" 203 " <string>Image</string>" 204 " <key>Caption</key>" 205 " <string>caption</string>" 206 " <key>GUID</key>\n" 207 " <string>guid1</string>" 208 " <key>ImagePath</key>" 209 " <string>/vol/path1.jpg</string>" 210 " <key>ThumbPath</key>" 211 " <string>/vol/thumb1.jpg</string>" 212 " </dict>\n" 213 " <key>2</key>\n" 214 " <dict>\n" 215 " <key>MediaType</key>" 216 " <string>Image</string>" 217 " <key>Caption</key>" 218 " <string>caption2</string>" 219 " <key>GUID</key>\n" 220 " <string>guid2</string>" 221 " <key>ImagePath</key>" 222 " <string>/vol/path2.jpg</string>" 223 " <key>ThumbPath</key>" 224 " <string>/vol/thumb2.jpg</string>" 225 " </dict>\n" 226 " <key>3</key>\n" 227 " <dict>\n" 228 " <key>MediaType</key>" 229 " <string>Image</string>" 230 " <key>Caption</key>" 231 " <string>caption3</string>" 232 " <key>GUID</key>\n" 233 " <string>guid3</string>" 234 " <key>ImagePath</key>" 235 " <string>/vol/path3.jpg</string>" 236 " <key>ThumbPath</key>" 237 " <string>/vol/thumb3.jpg</string>" 238 " </dict>\n" 239 " <key>4</key>\n" // A name duplicate of [3] in another path. 240 " <dict>\n" 241 " <key>MediaType</key>" 242 " <string>Image</string>" 243 " <key>Caption</key>" 244 " <string>caption</string>" 245 " <key>GUID</key>\n" 246 " <string>guid3</string>" 247 " <key>ImagePath</key>" 248 " <string>/vol/dupe/path3.jpg</string>" 249 " <key>ThumbPath</key>" 250 " <string>/vol/dupe/thumb3.jpg</string>" 251 " </dict>\n" 252 " <key>5</key>\n" // A name duplicate of [2] in another path. 253 " <dict>\n" 254 " <key>MediaType</key>" 255 " <string>Image</string>" 256 " <key>Caption</key>" 257 " <string>caption5</string>" 258 " <key>GUID</key>\n" 259 " <string>guid2</string>" 260 " <key>ImagePath</key>" 261 " <string>/vol/dupe/path2.jpg</string>" 262 " <key>ThumbPath</key>" 263 " <string>/vol/dupe/thumb2.jpg</string>" 264 " <key>OriginalPath</key>" \ 265 " <string>/original/vol/another2.jpg</string>" \ 266 " </dict>\n" 267 "</dict>\n" 268 "</dict></plist>\n"; 269 } 270 271 virtual void StartTest(bool parse_success) OVERRIDE { 272 EXPECT_TRUE(parse_success); 273 274 std::vector<std::string> names = data_provider()->GetAlbumNames(); 275 EXPECT_EQ(3U, names.size()); 276 EXPECT_EQ("Album1", names[0]); 277 278 EXPECT_EQ(FilePath("/vol/path1.jpg").value(), 279 data_provider()->GetPhotoLocationInAlbum( 280 "Album1", "path1.jpg").value()); 281 EXPECT_EQ(FilePath("/vol/path3.jpg").value(), 282 data_provider()->GetPhotoLocationInAlbum( 283 "Album1", "path3.jpg").value()); 284 EXPECT_EQ(FilePath("/vol/dupe/path3.jpg").value(), 285 data_provider()->GetPhotoLocationInAlbum( 286 "Album1", "path3(4).jpg").value()); 287 EXPECT_EQ(FilePath().value(), 288 data_provider()->GetPhotoLocationInAlbum( 289 "Album1", "path5.jpg").value()); 290 291 // path2.jpg is name-duped, but in different albums, and so should not 292 // be mangled. 293 EXPECT_EQ(FilePath("/vol/dupe/path2.jpg").value(), 294 data_provider()->GetPhotoLocationInAlbum( 295 "Album5", "path2.jpg").value()); 296 EXPECT_EQ(FilePath("/vol/path2.jpg").value(), 297 data_provider()->GetPhotoLocationInAlbum( 298 "Album2", "path2.jpg").value()); 299 300 std::map<std::string, base::FilePath> photos = 301 data_provider()->GetAlbumContents("nonexistent"); 302 EXPECT_EQ(0U, photos.size()); 303 photos = data_provider()->GetAlbumContents("Album1"); 304 EXPECT_EQ(3U, photos.size()); 305 EXPECT_TRUE(ContainsKey(photos, "path1.jpg")); 306 EXPECT_FALSE(ContainsKey(photos, "path2.jpg")); 307 EXPECT_TRUE(ContainsKey(photos, "path3.jpg")); 308 EXPECT_TRUE(ContainsKey(photos, "path3(4).jpg")); 309 EXPECT_EQ(FilePath("/vol/path1.jpg").value(), photos["path1.jpg"].value()); 310 EXPECT_EQ(FilePath("/vol/path3.jpg").value(), 311 photos["path3.jpg"].value()); 312 EXPECT_EQ(FilePath("/vol/dupe/path3.jpg").value(), 313 photos["path3(4).jpg"].value()); 314 315 photos = data_provider()->GetAlbumContents("Album2"); 316 EXPECT_EQ(1U, photos.size()); 317 EXPECT_TRUE(ContainsKey(photos, "path2.jpg")); 318 319 EXPECT_FALSE(data_provider()->HasOriginals("Album1")); 320 EXPECT_TRUE(data_provider()->HasOriginals("Album5")); 321 std::map<std::string, base::FilePath> originals = 322 data_provider()->GetOriginals("Album1"); 323 EXPECT_EQ(0U, originals.size()); 324 originals = data_provider()->GetOriginals("Album5"); 325 EXPECT_EQ(1U, originals.size()); 326 EXPECT_TRUE(ContainsKey(originals, "path2.jpg")); 327 EXPECT_FALSE(ContainsKey(originals, "path1.jpg")); 328 EXPECT_EQ(FilePath("/original/vol/another2.jpg").value(), 329 originals["path2.jpg"].value()); 330 base::FilePath original_path = 331 data_provider()->GetOriginalPhotoLocation("Album5", "path2.jpg"); 332 EXPECT_EQ(FilePath("/original/vol/another2.jpg").value(), 333 original_path.value()); 334 335 TestDone(); 336 } 337 338 private: 339 DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderBasicTest); 340 }; 341 342 class IPhotoDataProviderRefreshTest : public IPhotoDataProviderTest { 343 public: 344 IPhotoDataProviderRefreshTest() {} 345 virtual ~IPhotoDataProviderRefreshTest() {} 346 347 std::string another_album; 348 349 virtual std::string GetLibraryString() OVERRIDE { 350 return "<plist><dict>\n" 351 "<key>List of Albums</key>\n" 352 "<array>" 353 " <dict>" 354 " <key>AlbumId</key>" 355 " <integer>14</integer>" 356 " <key>AlbumName</key>" 357 " <string>Album1</string>" 358 " <key>KeyList</key>" 359 " <array>" 360 " <string>1</string>" 361 " </array>" 362 " </dict>\n" + 363 another_album + 364 "</array>\n" 365 "<key>Master Image List</key>\n" 366 "<dict>\n" 367 " <key>1</key>\n" 368 " <dict>\n" 369 " <key>MediaType</key>" 370 " <string>Image</string>" 371 " <key>Caption1</key>" 372 " <string>caption</string>" 373 " <key>GUID</key>\n" 374 " <string>guid1</string>" 375 " <key>ImagePath</key>" 376 " <string>/vol/path1.jpg</string>" 377 " <key>ThumbPath</key>" 378 " <string>/vol/thumb1.jpg</string>" 379 " </dict>\n" 380 "</dict>\n" 381 "</dict></plist>\n"; 382 } 383 384 virtual void StartTest(bool parse_success) OVERRIDE { 385 EXPECT_TRUE(parse_success); 386 387 EXPECT_EQ(FilePath("/vol/path1.jpg"), 388 data_provider()->GetPhotoLocationInAlbum("Album1", "path1.jpg")); 389 std::vector<std::string> names = data_provider()->GetAlbumNames(); 390 EXPECT_EQ(1U, names.size()); 391 EXPECT_EQ("Album1", names[0]); 392 393 another_album = 394 " <dict>" 395 " <key>AlbumId</key>" 396 " <integer>14</integer>" 397 " <key>AlbumName</key>" 398 " <string>Another Album</string>" 399 " <key>KeyList</key>" 400 " <array>" 401 " <string>1</string>" 402 " </array>" 403 " </dict>\n"; 404 405 WriteLibrary(base::Bind(&IPhotoDataProviderRefreshTest::CheckAfterWrite, 406 base::Unretained(this))); 407 } 408 409 void CheckAfterWrite() { 410 // No change -- data has not been parsed. 411 EXPECT_EQ(FilePath("/vol/path1.jpg"), 412 data_provider()->GetPhotoLocationInAlbum("Album1", "path1.jpg")); 413 std::vector<std::string> names = data_provider()->GetAlbumNames(); 414 EXPECT_EQ(1U, names.size()); 415 EXPECT_EQ("Album1", names[0]); 416 417 data_provider()->RefreshData( 418 base::Bind(&IPhotoDataProviderRefreshTest::CheckRefresh, 419 base::Unretained(this))); 420 } 421 422 void CheckRefresh(bool is_valid) { 423 EXPECT_TRUE(is_valid); 424 425 EXPECT_EQ(FilePath("/vol/path1.jpg"), 426 data_provider()->GetPhotoLocationInAlbum("Album1", "path1.jpg")); 427 std::vector<std::string> names = data_provider()->GetAlbumNames(); 428 EXPECT_EQ(2U, names.size()); 429 if (names.size() == 2U) { 430 EXPECT_EQ("Album1", names[0]); 431 EXPECT_EQ("Another Album", names[1]); 432 } 433 434 TestDone(); 435 } 436 437 private: 438 DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderRefreshTest); 439 }; 440 441 class IPhotoDataProviderInvalidTest : public IPhotoDataProviderTest { 442 public: 443 IPhotoDataProviderInvalidTest() {} 444 virtual ~IPhotoDataProviderInvalidTest() {} 445 446 virtual void StartTest(bool parse_success) OVERRIDE { 447 EXPECT_TRUE(parse_success); 448 449 SetLibraryChangeCallback( 450 base::Bind(&IPhotoDataProvider::RefreshData, 451 base::Unretained(data_provider()), 452 base::Bind(&IPhotoDataProviderInvalidTest::CheckInvalid, 453 base::Unretained(this)))); 454 EXPECT_EQ(1L, base::WriteFile(XmlFile(), " ", 1)); 455 } 456 457 void CheckInvalid(bool is_valid) { 458 EXPECT_FALSE(is_valid); 459 TestDone(); 460 } 461 462 private: 463 DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderInvalidTest); 464 }; 465 466 IN_PROC_BROWSER_TEST_F(IPhotoDataProviderBasicTest, BasicTest) { 467 RunTest(); 468 } 469 470 IN_PROC_BROWSER_TEST_F(IPhotoDataProviderRefreshTest, RefreshTest) { 471 RunTest(); 472 } 473 474 IN_PROC_BROWSER_TEST_F(IPhotoDataProviderInvalidTest, InvalidTest) { 475 RunTest(); 476 } 477 478 } // namespace iphoto 479