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 <set> 6#include <string> 7#include <vector> 8 9#include "base/bind_helpers.h" 10#include "base/files/file_path.h" 11#include "base/files/file_util.h" 12#include "base/files/scoped_temp_dir.h" 13#include "base/memory/ref_counted.h" 14#include "base/memory/scoped_vector.h" 15#include "base/message_loop/message_loop.h" 16#include "base/message_loop/message_loop_proxy.h" 17#include "base/run_loop.h" 18#include "base/strings/stringprintf.h" 19#include "base/time/time.h" 20#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" 21#include "chrome/browser/media_galleries/fileapi/media_path_filter.h" 22#include "chrome/browser/media_galleries/fileapi/picasa_data_provider.h" 23#include "chrome/browser/media_galleries/fileapi/picasa_file_util.h" 24#include "chrome/browser/media_galleries/imported_media_gallery_registry.h" 25#include "chrome/common/media_galleries/picasa_types.h" 26#include "chrome/common/media_galleries/pmp_constants.h" 27#include "content/public/browser/browser_thread.h" 28#include "content/public/test/mock_special_storage_policy.h" 29#include "content/public/test/test_browser_thread.h" 30#include "content/public/test/test_file_system_options.h" 31#include "storage/browser/fileapi/async_file_util.h" 32#include "storage/browser/fileapi/external_mount_points.h" 33#include "storage/browser/fileapi/file_system_context.h" 34#include "storage/browser/fileapi/file_system_operation_context.h" 35#include "storage/browser/fileapi/file_system_operation_runner.h" 36#include "storage/browser/fileapi/isolated_context.h" 37#include "storage/common/blob/shareable_file_reference.h" 38#include "testing/gtest/include/gtest/gtest.h" 39 40using storage::FileSystemOperationContext; 41using storage::FileSystemOperation; 42using storage::FileSystemURL; 43 44namespace picasa { 45 46namespace { 47 48base::Time::Exploded test_date_exploded = { 2013, 4, 0, 16, 0, 0, 0, 0 }; 49 50bool WriteJPEGHeader(const base::FilePath& path) { 51 const char kJpegHeader[] = "\xFF\xD8\xFF"; // Per HTML5 specification. 52 return base::WriteFile(path, kJpegHeader, arraysize(kJpegHeader)) != -1; 53} 54 55class TestFolder { 56 public: 57 TestFolder(const std::string& name, const base::Time& timestamp, 58 const std::string& uid, unsigned int image_files, 59 unsigned int non_image_files) 60 : name_(name), 61 timestamp_(timestamp), 62 uid_(uid), 63 image_files_(image_files), 64 non_image_files_(non_image_files), 65 folder_info_("", base::Time(), "", base::FilePath()) { 66 } 67 68 bool Init() { 69 if (!folder_dir_.CreateUniqueTempDir()) 70 return false; 71 72 folder_info_ = AlbumInfo(name_, timestamp_, uid_, folder_dir_.path()); 73 74 for (unsigned int i = 0; i < image_files_; ++i) { 75 std::string image_filename = base::StringPrintf("img%05d.jpg", i); 76 image_filenames_.insert(image_filename); 77 78 base::FilePath path = folder_dir_.path().AppendASCII(image_filename); 79 80 if (!WriteJPEGHeader(path)) 81 return false; 82 } 83 84 for (unsigned int i = 0; i < non_image_files_; ++i) { 85 base::FilePath path = folder_dir_.path().AppendASCII( 86 base::StringPrintf("hello%05d.txt", i)); 87 if (base::WriteFile(path, NULL, 0) == -1) 88 return false; 89 } 90 91 return true; 92 } 93 94 double GetVariantTimestamp() const { 95 DCHECK(!folder_dir_.path().empty()); 96 base::Time variant_epoch = base::Time::FromLocalExploded( 97 picasa::kPmpVariantTimeEpoch); 98 99 int64 microseconds_since_epoch = 100 (folder_info_.timestamp - variant_epoch).InMicroseconds(); 101 102 return static_cast<double>(microseconds_since_epoch) / 103 base::Time::kMicrosecondsPerDay; 104 } 105 106 const std::set<std::string>& image_filenames() const { 107 DCHECK(!folder_dir_.path().empty()); 108 return image_filenames_; 109 } 110 111 const AlbumInfo& folder_info() const { 112 DCHECK(!folder_dir_.path().empty()); 113 return folder_info_; 114 } 115 116 const base::Time& timestamp() const { 117 return timestamp_; 118 } 119 120 private: 121 const std::string name_; 122 const base::Time timestamp_; 123 const std::string uid_; 124 unsigned int image_files_; 125 unsigned int non_image_files_; 126 127 std::set<std::string> image_filenames_; 128 129 base::ScopedTempDir folder_dir_; 130 AlbumInfo folder_info_; 131}; 132 133void ReadDirectoryTestHelperCallback( 134 base::RunLoop* run_loop, 135 FileSystemOperation::FileEntryList* contents, 136 bool* completed, base::File::Error error, 137 const FileSystemOperation::FileEntryList& file_list, 138 bool has_more) { 139 DCHECK(!*completed); 140 *completed = !has_more && error == base::File::FILE_OK; 141 *contents = file_list; 142 run_loop->Quit(); 143} 144 145void ReadDirectoryTestHelper(storage::FileSystemOperationRunner* runner, 146 const FileSystemURL& url, 147 FileSystemOperation::FileEntryList* contents, 148 bool* completed) { 149 DCHECK(contents); 150 DCHECK(completed); 151 base::RunLoop run_loop; 152 runner->ReadDirectory( 153 url, base::Bind(&ReadDirectoryTestHelperCallback, &run_loop, contents, 154 completed)); 155 run_loop.Run(); 156} 157 158void SynchronouslyRunOnMediaTaskRunner(const base::Closure& closure) { 159 base::RunLoop loop; 160 MediaFileSystemBackend::MediaTaskRunner()->PostTaskAndReply( 161 FROM_HERE, 162 closure, 163 loop.QuitClosure()); 164 loop.Run(); 165} 166 167void CreateSnapshotFileTestHelperCallback( 168 base::RunLoop* run_loop, 169 base::File::Error* error, 170 base::FilePath* platform_path_result, 171 base::File::Error result, 172 const base::File::Info& file_info, 173 const base::FilePath& platform_path, 174 const scoped_refptr<storage::ShareableFileReference>& file_ref) { 175 DCHECK(run_loop); 176 DCHECK(error); 177 DCHECK(platform_path_result); 178 179 *error = result; 180 *platform_path_result = platform_path; 181 run_loop->Quit(); 182} 183 184} // namespace 185 186class TestPicasaFileUtil : public PicasaFileUtil { 187 public: 188 TestPicasaFileUtil(MediaPathFilter* media_path_filter, 189 PicasaDataProvider* data_provider) 190 : PicasaFileUtil(media_path_filter), 191 data_provider_(data_provider) { 192 } 193 virtual ~TestPicasaFileUtil() {} 194 private: 195 virtual PicasaDataProvider* GetDataProvider() OVERRIDE { 196 return data_provider_; 197 } 198 199 PicasaDataProvider* data_provider_; 200}; 201 202class TestMediaFileSystemBackend : public MediaFileSystemBackend { 203 public: 204 TestMediaFileSystemBackend(const base::FilePath& profile_path, 205 PicasaFileUtil* picasa_file_util) 206 : MediaFileSystemBackend(profile_path, 207 MediaFileSystemBackend::MediaTaskRunner().get()), 208 test_file_util_(picasa_file_util) {} 209 210 virtual storage::AsyncFileUtil* GetAsyncFileUtil( 211 storage::FileSystemType type) OVERRIDE { 212 if (type != storage::kFileSystemTypePicasa) 213 return NULL; 214 215 return test_file_util_.get(); 216 } 217 218 private: 219 scoped_ptr<storage::AsyncFileUtil> test_file_util_; 220}; 221 222class PicasaFileUtilTest : public testing::Test { 223 public: 224 PicasaFileUtilTest() 225 : io_thread_(content::BrowserThread::IO, &message_loop_) { 226 } 227 virtual ~PicasaFileUtilTest() {} 228 229 virtual void SetUp() OVERRIDE { 230 ASSERT_TRUE(profile_dir_.CreateUniqueTempDir()); 231 ImportedMediaGalleryRegistry::GetInstance()->Initialize(); 232 233 scoped_refptr<storage::SpecialStoragePolicy> storage_policy = 234 new content::MockSpecialStoragePolicy(); 235 236 SynchronouslyRunOnMediaTaskRunner(base::Bind( 237 &PicasaFileUtilTest::SetUpOnMediaTaskRunner, base::Unretained(this))); 238 239 media_path_filter_.reset(new MediaPathFilter()); 240 241 ScopedVector<storage::FileSystemBackend> additional_providers; 242 additional_providers.push_back(new TestMediaFileSystemBackend( 243 profile_dir_.path(), 244 new TestPicasaFileUtil(media_path_filter_.get(), 245 picasa_data_provider_.get()))); 246 247 file_system_context_ = new storage::FileSystemContext( 248 base::MessageLoopProxy::current().get(), 249 base::MessageLoopProxy::current().get(), 250 storage::ExternalMountPoints::CreateRefCounted().get(), 251 storage_policy.get(), 252 NULL, 253 additional_providers.Pass(), 254 std::vector<storage::URLRequestAutoMountHandler>(), 255 profile_dir_.path(), 256 content::CreateAllowFileAccessOptions()); 257 } 258 259 virtual void TearDown() OVERRIDE { 260 SynchronouslyRunOnMediaTaskRunner( 261 base::Bind(&PicasaFileUtilTest::TearDownOnMediaTaskRunner, 262 base::Unretained(this))); 263 } 264 265 protected: 266 void SetUpOnMediaTaskRunner() { 267 picasa_data_provider_.reset(new PicasaDataProvider(base::FilePath())); 268 } 269 270 void TearDownOnMediaTaskRunner() { 271 picasa_data_provider_.reset(); 272 } 273 274 // |test_folders| must be in alphabetical order for easy verification 275 void SetupFolders(ScopedVector<TestFolder>* test_folders, 276 const std::vector<AlbumInfo>& albums, 277 const AlbumImagesMap& albums_images) { 278 std::vector<AlbumInfo> folders; 279 for (ScopedVector<TestFolder>::iterator it = test_folders->begin(); 280 it != test_folders->end(); ++it) { 281 TestFolder* test_folder = *it; 282 ASSERT_TRUE(test_folder->Init()); 283 folders.push_back(test_folder->folder_info()); 284 } 285 286 PicasaDataProvider::UniquifyNames(albums, 287 &picasa_data_provider_->album_map_); 288 PicasaDataProvider::UniquifyNames(folders, 289 &picasa_data_provider_->folder_map_); 290 picasa_data_provider_->albums_images_ = albums_images; 291 picasa_data_provider_->state_ = 292 PicasaDataProvider::ALBUMS_IMAGES_FRESH_STATE; 293 } 294 295 void VerifyFolderDirectoryList(const ScopedVector<TestFolder>& test_folders) { 296 FileSystemOperation::FileEntryList contents; 297 FileSystemURL url = CreateURL(kPicasaDirFolders); 298 bool completed = false; 299 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 300 301 ASSERT_TRUE(completed); 302 ASSERT_EQ(test_folders.size(), contents.size()); 303 304 for (size_t i = 0; i < contents.size(); ++i) { 305 EXPECT_TRUE(contents[i].is_directory); 306 307 // Because the timestamp is written out as a floating point Microsoft 308 // variant time, we only expect it to be accurate to within a second. 309 base::TimeDelta delta = test_folders[i]->folder_info().timestamp - 310 contents[i].last_modified_time; 311 EXPECT_LT(delta, base::TimeDelta::FromSeconds(1)); 312 313 FileSystemOperation::FileEntryList folder_contents; 314 FileSystemURL folder_url = CreateURL( 315 std::string(kPicasaDirFolders) + "/" + 316 base::FilePath(contents[i].name).AsUTF8Unsafe()); 317 bool folder_read_completed = false; 318 ReadDirectoryTestHelper(operation_runner(), folder_url, &folder_contents, 319 &folder_read_completed); 320 321 EXPECT_TRUE(folder_read_completed); 322 323 const std::set<std::string>& image_filenames = 324 test_folders[i]->image_filenames(); 325 326 EXPECT_EQ(image_filenames.size(), folder_contents.size()); 327 328 for (FileSystemOperation::FileEntryList::const_iterator file_it = 329 folder_contents.begin(); file_it != folder_contents.end(); 330 ++file_it) { 331 EXPECT_EQ(1u, image_filenames.count( 332 base::FilePath(file_it->name).AsUTF8Unsafe())); 333 } 334 } 335 } 336 337 std::string DateToPathString(const base::Time& time) { 338 return PicasaDataProvider::DateToPathString(time); 339 } 340 341 void TestNonexistentDirectory(const std::string& path) { 342 FileSystemOperation::FileEntryList contents; 343 FileSystemURL url = CreateURL(path); 344 bool completed = false; 345 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 346 347 ASSERT_FALSE(completed); 348 } 349 350 void TestEmptyDirectory(const std::string& path) { 351 FileSystemOperation::FileEntryList contents; 352 FileSystemURL url = CreateURL(path); 353 bool completed = false; 354 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 355 356 ASSERT_TRUE(completed); 357 EXPECT_EQ(0u, contents.size()); 358 } 359 360 FileSystemURL CreateURL(const std::string& path) const { 361 base::FilePath virtual_path = 362 ImportedMediaGalleryRegistry::GetInstance()->ImportedRoot(); 363 virtual_path = virtual_path.AppendASCII("picasa"); 364 virtual_path = virtual_path.AppendASCII(path); 365 return file_system_context_->CreateCrackedFileSystemURL( 366 GURL("http://www.example.com"), 367 storage::kFileSystemTypePicasa, 368 virtual_path); 369 } 370 371 storage::FileSystemOperationRunner* operation_runner() const { 372 return file_system_context_->operation_runner(); 373 } 374 375 scoped_refptr<storage::FileSystemContext> file_system_context() const { 376 return file_system_context_; 377 } 378 379 private: 380 base::MessageLoop message_loop_; 381 content::TestBrowserThread io_thread_; 382 383 base::ScopedTempDir profile_dir_; 384 385 scoped_refptr<storage::FileSystemContext> file_system_context_; 386 scoped_ptr<PicasaDataProvider> picasa_data_provider_; 387 scoped_ptr<MediaPathFilter> media_path_filter_; 388 389 DISALLOW_COPY_AND_ASSIGN(PicasaFileUtilTest); 390}; 391 392TEST_F(PicasaFileUtilTest, DateFormat) { 393 base::Time::Exploded exploded_shortmonth = { 2013, 4, 0, 16, 0, 0, 0, 0 }; 394 base::Time shortmonth = base::Time::FromLocalExploded(exploded_shortmonth); 395 396 base::Time::Exploded exploded_shortday = { 2013, 11, 0, 3, 0, 0, 0, 0 }; 397 base::Time shortday = base::Time::FromLocalExploded(exploded_shortday); 398 399 EXPECT_EQ("2013-04-16", DateToPathString(shortmonth)); 400 EXPECT_EQ("2013-11-03", DateToPathString(shortday)); 401} 402 403TEST_F(PicasaFileUtilTest, NameDeduplication) { 404 ScopedVector<TestFolder> test_folders; 405 std::vector<std::string> expected_names; 406 407 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 408 base::Time test_date_2 = test_date - base::TimeDelta::FromDays(1); 409 410 std::string test_date_string = DateToPathString(test_date); 411 std::string test_date_2_string = DateToPathString(test_date_2); 412 413 test_folders.push_back( 414 new TestFolder("diff_date", test_date_2, "uuid3", 0, 0)); 415 expected_names.push_back("diff_date " + test_date_2_string); 416 417 test_folders.push_back( 418 new TestFolder("diff_date", test_date, "uuid2", 0, 0)); 419 expected_names.push_back("diff_date " + test_date_string); 420 421 test_folders.push_back( 422 new TestFolder("duplicate", test_date, "uuid4", 0, 0)); 423 expected_names.push_back("duplicate " + test_date_string + " (1)"); 424 425 test_folders.push_back( 426 new TestFolder("duplicate", test_date, "uuid5", 0, 0)); 427 expected_names.push_back("duplicate " + test_date_string + " (2)"); 428 429 test_folders.push_back( 430 new TestFolder("unique_name", test_date, "uuid1", 0, 0)); 431 expected_names.push_back("unique_name " + test_date_string); 432 433 SetupFolders(&test_folders, std::vector<AlbumInfo>(), AlbumImagesMap()); 434 435 FileSystemOperation::FileEntryList contents; 436 FileSystemURL url = CreateURL(kPicasaDirFolders); 437 bool completed = false; 438 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 439 440 ASSERT_TRUE(completed); 441 ASSERT_EQ(expected_names.size(), contents.size()); 442 for (size_t i = 0; i < contents.size(); ++i) { 443 EXPECT_EQ(expected_names[i], 444 base::FilePath(contents[i].name).AsUTF8Unsafe()); 445 EXPECT_EQ(test_folders[i]->timestamp(), contents[i].last_modified_time); 446 EXPECT_TRUE(contents[i].is_directory); 447 } 448} 449 450TEST_F(PicasaFileUtilTest, RootFolders) { 451 ScopedVector<TestFolder> empty_folders_list; 452 SetupFolders(&empty_folders_list, std::vector<AlbumInfo>(), AlbumImagesMap()); 453 454 FileSystemOperation::FileEntryList contents; 455 FileSystemURL url = CreateURL(""); 456 bool completed = false; 457 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 458 459 ASSERT_TRUE(completed); 460 ASSERT_EQ(2u, contents.size()); 461 462 EXPECT_TRUE(contents.front().is_directory); 463 EXPECT_TRUE(contents.back().is_directory); 464 465 EXPECT_EQ(0, contents.front().size); 466 EXPECT_EQ(0, contents.back().size); 467 468 EXPECT_EQ(FILE_PATH_LITERAL("albums"), contents.front().name); 469 EXPECT_EQ(FILE_PATH_LITERAL("folders"), contents.back().name); 470} 471 472TEST_F(PicasaFileUtilTest, NonexistentFolder) { 473 ScopedVector<TestFolder> empty_folders_list; 474 SetupFolders(&empty_folders_list, std::vector<AlbumInfo>(), AlbumImagesMap()); 475 476 TestNonexistentDirectory(std::string(kPicasaDirFolders) + "/foo"); 477 TestNonexistentDirectory(std::string(kPicasaDirFolders) + "/foo/bar"); 478 TestNonexistentDirectory(std::string(kPicasaDirFolders) + "/foo/bar/baz"); 479} 480 481TEST_F(PicasaFileUtilTest, FolderContentsTrivial) { 482 ScopedVector<TestFolder> test_folders; 483 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 484 485 test_folders.push_back( 486 new TestFolder("folder-1-empty", test_date, "uid-empty", 0, 0)); 487 test_folders.push_back( 488 new TestFolder("folder-2-images", test_date, "uid-images", 5, 0)); 489 test_folders.push_back( 490 new TestFolder("folder-3-nonimages", test_date, "uid-nonimages", 0, 5)); 491 test_folders.push_back( 492 new TestFolder("folder-4-both", test_date, "uid-both", 5, 5)); 493 494 SetupFolders(&test_folders, std::vector<AlbumInfo>(), AlbumImagesMap()); 495 VerifyFolderDirectoryList(test_folders); 496} 497 498TEST_F(PicasaFileUtilTest, FolderWithManyFiles) { 499 ScopedVector<TestFolder> test_folders; 500 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 501 502 test_folders.push_back( 503 new TestFolder("folder-many-files", test_date, "uid-both", 50, 50)); 504 505 SetupFolders(&test_folders, std::vector<AlbumInfo>(), AlbumImagesMap()); 506 VerifyFolderDirectoryList(test_folders); 507} 508 509TEST_F(PicasaFileUtilTest, ManyFolders) { 510 ScopedVector<TestFolder> test_folders; 511 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 512 513 for (unsigned int i = 0; i < 50; ++i) { 514 base::Time date = test_date - base::TimeDelta::FromDays(i); 515 516 test_folders.push_back( 517 new TestFolder(base::StringPrintf("folder-%05d", i), 518 date, 519 base::StringPrintf("uid%05d", i), i % 5, i % 3)); 520 } 521 522 SetupFolders(&test_folders, std::vector<AlbumInfo>(), AlbumImagesMap()); 523 VerifyFolderDirectoryList(test_folders); 524} 525 526TEST_F(PicasaFileUtilTest, AlbumExistence) { 527 ScopedVector<TestFolder> test_folders; 528 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 529 530 std::vector<AlbumInfo> albums; 531 AlbumInfo info; 532 info.name = "albumname"; 533 info.uid = "albumuid"; 534 info.timestamp = test_date; 535 albums.push_back(info); 536 537 AlbumImagesMap albums_images; 538 albums_images[info.uid] = AlbumImages(); 539 540 SetupFolders(&test_folders, albums, albums_images); 541 542 TestEmptyDirectory(std::string(kPicasaDirAlbums) + "/albumname 2013-04-16"); 543 TestNonexistentDirectory(std::string(kPicasaDirAlbums) + 544 "/albumname 2013-04-16/toodeep"); 545 TestNonexistentDirectory(std::string(kPicasaDirAlbums) + "/wrongname"); 546} 547 548TEST_F(PicasaFileUtilTest, AlbumContents) { 549 ScopedVector<TestFolder> test_folders; 550 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 551 552 std::vector<AlbumInfo> albums; 553 AlbumInfo info; 554 info.name = "albumname"; 555 info.uid = "albumuid"; 556 info.timestamp = test_date; 557 albums.push_back(info); 558 559 base::ScopedTempDir temp_dir; 560 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 561 562 base::FilePath image_path = temp_dir.path().AppendASCII("img.jpg"); 563 ASSERT_TRUE(WriteJPEGHeader(image_path)); 564 565 AlbumImagesMap albums_images; 566 albums_images[info.uid] = AlbumImages(); 567 albums_images[info.uid]["mapped_name.jpg"] = image_path; 568 569 SetupFolders(&test_folders, albums, albums_images); 570 571 FileSystemOperation::FileEntryList contents; 572 FileSystemURL url = 573 CreateURL(std::string(kPicasaDirAlbums) + "/albumname 2013-04-16"); 574 bool completed = false; 575 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 576 577 ASSERT_TRUE(completed); 578 EXPECT_EQ(1u, contents.size()); 579 EXPECT_EQ("mapped_name.jpg", 580 base::FilePath(contents.begin()->name).AsUTF8Unsafe()); 581 EXPECT_FALSE(contents.begin()->is_directory); 582 583 // Create a snapshot file to verify the file path. 584 base::RunLoop loop; 585 base::File::Error error; 586 base::FilePath platform_path_result; 587 storage::FileSystemOperationRunner::SnapshotFileCallback snapshot_callback = 588 base::Bind(&CreateSnapshotFileTestHelperCallback, 589 &loop, 590 &error, 591 &platform_path_result); 592 operation_runner()->CreateSnapshotFile( 593 CreateURL(std::string(kPicasaDirAlbums) + 594 "/albumname 2013-04-16/mapped_name.jpg"), 595 snapshot_callback); 596 loop.Run(); 597 EXPECT_EQ(base::File::FILE_OK, error); 598 EXPECT_EQ(image_path, platform_path_result); 599} 600 601} // namespace picasa 602