Home | History | Annotate | Download | only in picasa
      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