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/file_util.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/memory/scoped_vector.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/message_loop/message_loop_proxy.h" 15 #include "base/run_loop.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/time/time.h" 18 #include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" 19 #include "chrome/browser/media_galleries/fileapi/media_path_filter.h" 20 #include "chrome/browser/media_galleries/fileapi/picasa/picasa_data_provider.h" 21 #include "chrome/browser/media_galleries/fileapi/picasa/picasa_file_util.h" 22 #include "chrome/common/media_galleries/picasa_types.h" 23 #include "chrome/common/media_galleries/pmp_constants.h" 24 #include "content/public/browser/browser_thread.h" 25 #include "content/public/test/test_browser_thread.h" 26 #include "testing/gtest/include/gtest/gtest.h" 27 #include "webkit/browser/fileapi/async_file_util_adapter.h" 28 #include "webkit/browser/fileapi/external_mount_points.h" 29 #include "webkit/browser/fileapi/file_system_context.h" 30 #include "webkit/browser/fileapi/file_system_file_util.h" 31 #include "webkit/browser/fileapi/file_system_operation_context.h" 32 #include "webkit/browser/fileapi/file_system_operation_runner.h" 33 #include "webkit/browser/fileapi/isolated_context.h" 34 #include "webkit/browser/fileapi/mock_file_system_options.h" 35 #include "webkit/browser/quota/mock_special_storage_policy.h" 36 37 using fileapi::FileSystemFileUtil; 38 using fileapi::FileSystemOperationContext; 39 using fileapi::FileSystemOperation; 40 using fileapi::FileSystemURL; 41 42 namespace picasa { 43 44 namespace { 45 46 base::Time::Exploded test_date_exploded = { 2013, 4, 0, 16, 0, 0, 0, 0 }; 47 48 class TestFolder { 49 public: 50 TestFolder(const std::string& name, const base::Time& timestamp, 51 const std::string& uid, unsigned int image_files, 52 unsigned int non_image_files) 53 : name_(name), 54 timestamp_(timestamp), 55 uid_(uid), 56 image_files_(image_files), 57 non_image_files_(non_image_files), 58 folder_info_("", base::Time(), "", base::FilePath()) { 59 } 60 61 bool Init() { 62 if (!folder_dir_.CreateUniqueTempDir()) 63 return false; 64 65 folder_info_ = AlbumInfo(name_, timestamp_, uid_, folder_dir_.path()); 66 67 const char kJpegHeader[] = "\xFF\xD8\xFF"; // Per HTML5 specification. 68 for (unsigned int i = 0; i < image_files_; ++i) { 69 std::string image_filename = base::StringPrintf("img%05d.jpg", i); 70 image_filenames_.insert(image_filename); 71 72 base::FilePath path = folder_dir_.path().AppendASCII(image_filename); 73 74 if (file_util::WriteFile(path, kJpegHeader, arraysize(kJpegHeader)) == -1) 75 return false; 76 } 77 78 for (unsigned int i = 0; i < non_image_files_; ++i) { 79 base::FilePath path = folder_dir_.path().AppendASCII( 80 base::StringPrintf("hello%05d.txt", i)); 81 if (file_util::WriteFile(path, NULL, 0) == -1) 82 return false; 83 } 84 85 return true; 86 } 87 88 double GetVariantTimestamp() const { 89 DCHECK(!folder_dir_.path().empty()); 90 base::Time variant_epoch = base::Time::FromLocalExploded( 91 picasa::kPmpVariantTimeEpoch); 92 93 int64 microseconds_since_epoch = 94 (folder_info_.timestamp - variant_epoch).InMicroseconds(); 95 96 return static_cast<double>(microseconds_since_epoch) / 97 base::Time::kMicrosecondsPerDay; 98 } 99 100 const std::set<std::string>& image_filenames() const { 101 DCHECK(!folder_dir_.path().empty()); 102 return image_filenames_; 103 } 104 105 const AlbumInfo& folder_info() const { 106 DCHECK(!folder_dir_.path().empty()); 107 return folder_info_; 108 } 109 110 const base::Time& timestamp() const { 111 return timestamp_; 112 } 113 114 private: 115 const std::string name_; 116 const base::Time timestamp_; 117 const std::string uid_; 118 unsigned int image_files_; 119 unsigned int non_image_files_; 120 121 std::set<std::string> image_filenames_; 122 123 base::ScopedTempDir folder_dir_; 124 AlbumInfo folder_info_; 125 }; 126 127 void ReadDirectoryTestHelperCallback( 128 base::RunLoop* run_loop, 129 FileSystemOperation::FileEntryList* contents, 130 bool* completed, base::PlatformFileError error, 131 const FileSystemOperation::FileEntryList& file_list, 132 bool has_more) { 133 DCHECK(!*completed); 134 *completed = !has_more && error == base::PLATFORM_FILE_OK; 135 *contents = file_list; 136 run_loop->Quit(); 137 } 138 139 void ReadDirectoryTestHelper(fileapi::FileSystemOperationRunner* runner, 140 const FileSystemURL& url, 141 FileSystemOperation::FileEntryList* contents, 142 bool* completed) { 143 DCHECK(contents); 144 DCHECK(completed); 145 base::RunLoop run_loop; 146 runner->ReadDirectory( 147 url, base::Bind(&ReadDirectoryTestHelperCallback, &run_loop, contents, 148 completed)); 149 run_loop.Run(); 150 } 151 152 } // namespace 153 154 class TestPicasaDataProvider : public PicasaDataProvider { 155 public: 156 TestPicasaDataProvider() 157 : PicasaDataProvider(base::FilePath(FILE_PATH_LITERAL("Fake"))), 158 initialized_(false) { 159 } 160 161 virtual ~TestPicasaDataProvider() {} 162 163 virtual void RefreshData(const base::Closure& ready_callback) OVERRIDE { 164 DCHECK(initialized_); 165 ready_callback.Run(); 166 } 167 168 void Init(const std::vector<AlbumInfo>& albums, 169 const std::vector<AlbumInfo>& folders) { 170 UniquifyNames(albums, &album_map_); 171 UniquifyNames(folders, &folder_map_); 172 initialized_ = true; 173 } 174 175 private: 176 bool initialized_; 177 }; 178 179 class TestPicasaFileUtil : public PicasaFileUtil { 180 public: 181 TestPicasaFileUtil(chrome::MediaPathFilter* media_path_filter, 182 PicasaDataProvider* data_provider) 183 : PicasaFileUtil(media_path_filter), 184 data_provider_(data_provider) { 185 } 186 virtual ~TestPicasaFileUtil() {} 187 private: 188 virtual PicasaDataProvider* GetDataProvider() OVERRIDE { 189 return data_provider_; 190 } 191 192 PicasaDataProvider* data_provider_; 193 }; 194 195 class TestMediaFileSystemBackend 196 : public chrome::MediaFileSystemBackend { 197 public: 198 TestMediaFileSystemBackend(const base::FilePath& profile_path, 199 PicasaFileUtil* picasa_file_util) 200 : chrome::MediaFileSystemBackend( 201 profile_path, 202 chrome::MediaFileSystemBackend::MediaTaskRunner().get()), 203 test_file_util_(picasa_file_util) {} 204 205 virtual fileapi::AsyncFileUtil* 206 GetAsyncFileUtil(fileapi::FileSystemType type) OVERRIDE { 207 if (type != fileapi::kFileSystemTypePicasa) 208 return NULL; 209 210 return test_file_util_.get(); 211 } 212 213 private: 214 scoped_ptr<fileapi::AsyncFileUtil> test_file_util_; 215 }; 216 217 class PicasaFileUtilTest : public testing::Test { 218 public: 219 PicasaFileUtilTest() 220 : io_thread_(content::BrowserThread::IO, &message_loop_) { 221 } 222 virtual ~PicasaFileUtilTest() {} 223 224 virtual void SetUp() OVERRIDE { 225 ASSERT_TRUE(profile_dir_.CreateUniqueTempDir()); 226 227 scoped_refptr<quota::SpecialStoragePolicy> storage_policy = 228 new quota::MockSpecialStoragePolicy(); 229 230 media_path_filter_.reset(new chrome::MediaPathFilter()); 231 picasa_data_provider_.reset(new TestPicasaDataProvider()); 232 233 ScopedVector<fileapi::FileSystemBackend> additional_providers; 234 additional_providers.push_back(new TestMediaFileSystemBackend( 235 profile_dir_.path(), 236 new TestPicasaFileUtil(media_path_filter_.get(), 237 picasa_data_provider_.get()))); 238 239 file_system_context_ = new fileapi::FileSystemContext( 240 base::MessageLoopProxy::current().get(), 241 base::MessageLoopProxy::current().get(), 242 fileapi::ExternalMountPoints::CreateRefCounted().get(), 243 storage_policy.get(), 244 NULL, 245 additional_providers.Pass(), 246 profile_dir_.path(), 247 fileapi::CreateAllowFileAccessOptions()); 248 } 249 250 protected: 251 // |test_folders| must be in alphabetical order for easy verification 252 void SetupFolders(ScopedVector<TestFolder>* test_folders) { 253 std::vector<AlbumInfo> folders; 254 for (ScopedVector<TestFolder>::iterator it = test_folders->begin(); 255 it != test_folders->end(); ++it) { 256 TestFolder* test_folder = *it; 257 ASSERT_TRUE(test_folder->Init()); 258 folders.push_back(test_folder->folder_info()); 259 } 260 picasa_data_provider_->Init(std::vector<AlbumInfo>(), folders); 261 } 262 263 void VerifyFolderDirectoryList(const ScopedVector<TestFolder>& test_folders) { 264 FileSystemOperation::FileEntryList contents; 265 FileSystemURL url = CreateURL(kPicasaDirFolders); 266 bool completed = false; 267 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 268 269 ASSERT_TRUE(completed); 270 ASSERT_EQ(test_folders.size(), contents.size()); 271 272 for (size_t i = 0; i < contents.size(); ++i) { 273 EXPECT_TRUE(contents[i].is_directory); 274 275 // Because the timestamp is written out as a floating point Microsoft 276 // variant time, we only expect it to be accurate to within a second. 277 base::TimeDelta delta = test_folders[i]->folder_info().timestamp - 278 contents[i].last_modified_time; 279 EXPECT_LT(delta, base::TimeDelta::FromSeconds(1)); 280 281 FileSystemOperation::FileEntryList folder_contents; 282 FileSystemURL folder_url = CreateURL( 283 std::string(kPicasaDirFolders) + "/" + 284 base::FilePath(contents[i].name).AsUTF8Unsafe()); 285 bool folder_read_completed = false; 286 ReadDirectoryTestHelper(operation_runner(), folder_url, &folder_contents, 287 &folder_read_completed); 288 289 EXPECT_TRUE(folder_read_completed); 290 291 const std::set<std::string>& image_filenames = 292 test_folders[i]->image_filenames(); 293 294 EXPECT_EQ(image_filenames.size(), folder_contents.size()); 295 296 for (FileSystemOperation::FileEntryList::const_iterator file_it = 297 folder_contents.begin(); file_it != folder_contents.end(); 298 ++file_it) { 299 EXPECT_EQ(1u, image_filenames.count( 300 base::FilePath(file_it->name).AsUTF8Unsafe())); 301 } 302 } 303 } 304 305 std::string DateToPathString(const base::Time& time) { 306 return PicasaDataProvider::DateToPathString(time); 307 } 308 309 void TestNonexistentFolder(const std::string& path_append) { 310 FileSystemOperation::FileEntryList contents; 311 FileSystemURL url = CreateURL( 312 std::string(kPicasaDirFolders) + path_append); 313 bool completed = false; 314 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 315 316 ASSERT_FALSE(completed); 317 } 318 319 FileSystemURL CreateURL(const std::string& virtual_path) const { 320 return file_system_context_->CreateCrackedFileSystemURL( 321 GURL("http://www.example.com"), fileapi::kFileSystemTypePicasa, 322 base::FilePath::FromUTF8Unsafe(virtual_path)); 323 } 324 325 fileapi::FileSystemOperationRunner* operation_runner() const { 326 return file_system_context_->operation_runner(); 327 } 328 329 scoped_refptr<fileapi::FileSystemContext> file_system_context() const { 330 return file_system_context_; 331 } 332 333 private: 334 base::MessageLoop message_loop_; 335 content::TestBrowserThread io_thread_; 336 337 base::ScopedTempDir profile_dir_; 338 339 scoped_refptr<fileapi::FileSystemContext> file_system_context_; 340 scoped_ptr<chrome::MediaPathFilter> media_path_filter_; 341 scoped_ptr<TestPicasaDataProvider> picasa_data_provider_; 342 343 DISALLOW_COPY_AND_ASSIGN(PicasaFileUtilTest); 344 }; 345 346 TEST_F(PicasaFileUtilTest, DateFormat) { 347 base::Time::Exploded exploded_shortmonth = { 2013, 4, 0, 16, 0, 0, 0, 0 }; 348 base::Time shortmonth = base::Time::FromLocalExploded(exploded_shortmonth); 349 350 base::Time::Exploded exploded_shortday = { 2013, 11, 0, 3, 0, 0, 0, 0 }; 351 base::Time shortday = base::Time::FromLocalExploded(exploded_shortday); 352 353 EXPECT_EQ("2013-04-16", DateToPathString(shortmonth)); 354 EXPECT_EQ("2013-11-03", DateToPathString(shortday)); 355 } 356 357 TEST_F(PicasaFileUtilTest, NameDeduplication) { 358 ScopedVector<TestFolder> test_folders; 359 std::vector<std::string> expected_names; 360 361 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 362 base::Time test_date_2 = test_date - base::TimeDelta::FromDays(1); 363 364 std::string test_date_string = DateToPathString(test_date); 365 std::string test_date_2_string = DateToPathString(test_date_2); 366 367 test_folders.push_back( 368 new TestFolder("diff_date", test_date_2, "uuid3", 0, 0)); 369 expected_names.push_back("diff_date " + test_date_2_string); 370 371 test_folders.push_back( 372 new TestFolder("diff_date", test_date, "uuid2", 0, 0)); 373 expected_names.push_back("diff_date " + test_date_string); 374 375 test_folders.push_back( 376 new TestFolder("duplicate", test_date, "uuid4", 0, 0)); 377 expected_names.push_back("duplicate " + test_date_string + " (1)"); 378 379 test_folders.push_back( 380 new TestFolder("duplicate", test_date, "uuid5", 0, 0)); 381 expected_names.push_back("duplicate " + test_date_string + " (2)"); 382 383 test_folders.push_back( 384 new TestFolder("unique_name", test_date, "uuid1", 0, 0)); 385 expected_names.push_back("unique_name " + test_date_string); 386 387 SetupFolders(&test_folders); 388 389 FileSystemOperation::FileEntryList contents; 390 FileSystemURL url = CreateURL(kPicasaDirFolders); 391 bool completed = false; 392 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 393 394 ASSERT_TRUE(completed); 395 ASSERT_EQ(expected_names.size(), contents.size()); 396 for (size_t i = 0; i < contents.size(); ++i) { 397 EXPECT_EQ(expected_names[i], 398 base::FilePath(contents[i].name).AsUTF8Unsafe()); 399 EXPECT_EQ(test_folders[i]->timestamp(), contents[i].last_modified_time); 400 EXPECT_TRUE(contents[i].is_directory); 401 } 402 } 403 404 TEST_F(PicasaFileUtilTest, RootFolders) { 405 ScopedVector<TestFolder> empty_folders_list; 406 SetupFolders(&empty_folders_list); 407 408 FileSystemOperation::FileEntryList contents; 409 FileSystemURL url = CreateURL(""); 410 bool completed = false; 411 ReadDirectoryTestHelper(operation_runner(), url, &contents, &completed); 412 413 ASSERT_TRUE(completed); 414 ASSERT_EQ(2u, contents.size()); 415 416 EXPECT_TRUE(contents.front().is_directory); 417 EXPECT_TRUE(contents.back().is_directory); 418 419 EXPECT_EQ(0, contents.front().size); 420 EXPECT_EQ(0, contents.back().size); 421 422 EXPECT_EQ(FILE_PATH_LITERAL("albums"), contents.front().name); 423 EXPECT_EQ(FILE_PATH_LITERAL("folders"), contents.back().name); 424 } 425 426 TEST_F(PicasaFileUtilTest, NonexistentFolder) { 427 ScopedVector<TestFolder> empty_folders_list; 428 SetupFolders(&empty_folders_list); 429 430 TestNonexistentFolder("/foo"); 431 TestNonexistentFolder("/foo/bar"); 432 TestNonexistentFolder("/foo/bar/baz"); 433 } 434 435 TEST_F(PicasaFileUtilTest, FolderContentsTrivial) { 436 ScopedVector<TestFolder> test_folders; 437 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 438 439 test_folders.push_back( 440 new TestFolder("folder-1-empty", test_date, "uid-empty", 0, 0)); 441 test_folders.push_back( 442 new TestFolder("folder-2-images", test_date, "uid-images", 5, 0)); 443 test_folders.push_back( 444 new TestFolder("folder-3-nonimages", test_date, "uid-nonimages", 0, 5)); 445 test_folders.push_back( 446 new TestFolder("folder-4-both", test_date, "uid-both", 5, 5)); 447 448 SetupFolders(&test_folders); 449 VerifyFolderDirectoryList(test_folders); 450 } 451 452 TEST_F(PicasaFileUtilTest, FolderWithManyFiles) { 453 ScopedVector<TestFolder> test_folders; 454 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 455 456 test_folders.push_back( 457 new TestFolder("folder-many-files", test_date, "uid-both", 500, 500)); 458 459 SetupFolders(&test_folders); 460 VerifyFolderDirectoryList(test_folders); 461 } 462 463 TEST_F(PicasaFileUtilTest, ManyFolders) { 464 ScopedVector<TestFolder> test_folders; 465 base::Time test_date = base::Time::FromLocalExploded(test_date_exploded); 466 467 // TODO(tommycli): Turn number of test folders back up to 50 (or more) once 468 // https://codereview.chromium.org/15479003/ lands. 469 for (unsigned int i = 0; i < 25; ++i) { 470 base::Time date = test_date - base::TimeDelta::FromDays(i); 471 472 test_folders.push_back( 473 new TestFolder(base::StringPrintf("folder-%05d", i), 474 date, 475 base::StringPrintf("uid%05d", i), i % 5, i % 3)); 476 } 477 478 SetupFolders(&test_folders); 479 VerifyFolderDirectoryList(test_folders); 480 } 481 482 } // namespace picasa 483