Home | History | Annotate | Download | only in media_galleries
      1 // Copyright 2014 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 "chrome/browser/media_galleries/media_folder_finder.h"
      6 
      7 #include <algorithm>
      8 #include <set>
      9 
     10 #include "base/file_util.h"
     11 #include "base/files/file_enumerator.h"
     12 #include "base/path_service.h"
     13 #include "base/sequence_checker.h"
     14 #include "base/stl_util.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/task_runner_util.h"
     17 #include "base/threading/sequenced_worker_pool.h"
     18 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
     19 #include "chrome/browser/media_galleries/fileapi/media_path_filter.h"
     20 #include "chrome/common/chrome_paths.h"
     21 #include "components/storage_monitor/storage_monitor.h"
     22 #include "content/public/browser/browser_thread.h"
     23 
     24 #if defined(OS_CHROMEOS)
     25 #include "chrome/common/chrome_paths.h"
     26 #include "chromeos/dbus/cros_disks_client.h"
     27 #endif
     28 
     29 using storage_monitor::StorageInfo;
     30 using storage_monitor::StorageMonitor;
     31 
     32 typedef base::Callback<void(const std::vector<base::FilePath>& /*roots*/)>
     33     DefaultScanRootsCallback;
     34 using content::BrowserThread;
     35 
     36 namespace {
     37 
     38 const int64 kMinimumImageSize = 200 * 1024;    // 200 KB
     39 const int64 kMinimumAudioSize = 500 * 1024;    // 500 KB
     40 const int64 kMinimumVideoSize = 1024 * 1024;   // 1 MB
     41 
     42 const int kPrunedPaths[] = {
     43 #if defined(OS_WIN)
     44   base::DIR_IE_INTERNET_CACHE,
     45   base::DIR_PROGRAM_FILES,
     46   base::DIR_PROGRAM_FILESX86,
     47   base::DIR_WINDOWS,
     48 #endif
     49 #if defined(OS_MACOSX) && !defined(OS_IOS)
     50   chrome::DIR_USER_APPLICATIONS,
     51   chrome::DIR_USER_LIBRARY,
     52 #endif
     53 #if defined(OS_LINUX)
     54   base::DIR_CACHE,
     55 #endif
     56 #if defined(OS_WIN) || defined(OS_LINUX)
     57   base::DIR_TEMP,
     58 #endif
     59 };
     60 
     61 bool IsValidScanPath(const base::FilePath& path) {
     62   return !path.empty() && path.IsAbsolute();
     63 }
     64 
     65 void CountScanResult(MediaGalleryScanFileType type,
     66                      MediaGalleryScanResult* scan_result) {
     67   if (type & MEDIA_GALLERY_SCAN_FILE_TYPE_IMAGE)
     68     scan_result->image_count += 1;
     69   if (type & MEDIA_GALLERY_SCAN_FILE_TYPE_AUDIO)
     70     scan_result->audio_count += 1;
     71   if (type & MEDIA_GALLERY_SCAN_FILE_TYPE_VIDEO)
     72     scan_result->video_count += 1;
     73 }
     74 
     75 bool FileMeetsSizeRequirement(MediaGalleryScanFileType type, int64 size) {
     76   if (type & MEDIA_GALLERY_SCAN_FILE_TYPE_IMAGE)
     77     if (size >= kMinimumImageSize)
     78       return true;
     79   if (type & MEDIA_GALLERY_SCAN_FILE_TYPE_AUDIO)
     80     if (size >= kMinimumAudioSize)
     81       return true;
     82   if (type & MEDIA_GALLERY_SCAN_FILE_TYPE_VIDEO)
     83     if (size >= kMinimumVideoSize)
     84       return true;
     85   return false;
     86 }
     87 
     88 // Return true if |path| should not be considered as the starting point for a
     89 // media scan.
     90 bool ShouldIgnoreScanRoot(const base::FilePath& path) {
     91 #if defined(OS_MACOSX)
     92   // Scanning root is of little value.
     93   return (path.value() == "/");
     94 #elif defined(OS_CHROMEOS)
     95   // Sanity check to make sure mount points are where they should be.
     96   base::FilePath mount_point =
     97       chromeos::CrosDisksClient::GetRemovableDiskMountPoint();
     98   return mount_point.IsParent(path);
     99 #elif defined(OS_LINUX)
    100   // /media and /mnt are likely the only places with interesting mount points.
    101   if (StartsWithASCII(path.value(), "/media", true) ||
    102       StartsWithASCII(path.value(), "/mnt", true)) {
    103     return false;
    104   }
    105   return true;
    106 #elif defined(OS_WIN)
    107   return false;
    108 #else
    109   NOTIMPLEMENTED();
    110   return false;
    111 #endif
    112 }
    113 
    114 // Return a location that is likely to have user data to scan, if any.
    115 base::FilePath GetPlatformSpecificDefaultScanRoot() {
    116   base::FilePath root;
    117 #if defined(OS_CHROMEOS)
    118   PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS_SAFE, &root);
    119 #elif defined(OS_MACOSX) || defined(OS_LINUX)
    120   PathService::Get(base::DIR_HOME, &root);
    121 #elif defined(OS_WIN)
    122   // Nothing to add.
    123 #else
    124   NOTIMPLEMENTED();
    125 #endif
    126   return root;
    127 }
    128 
    129 // Find the likely locations with user media files and pass them to
    130 // |callback|. Locations are platform specific.
    131 void GetDefaultScanRoots(const DefaultScanRootsCallback& callback,
    132                          bool has_override,
    133                          const std::vector<base::FilePath>& override_paths) {
    134   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    135 
    136   if (has_override) {
    137     callback.Run(override_paths);
    138     return;
    139   }
    140 
    141   StorageMonitor* monitor = StorageMonitor::GetInstance();
    142   DCHECK(monitor->IsInitialized());
    143 
    144   std::vector<base::FilePath> roots;
    145   std::vector<StorageInfo> storages = monitor->GetAllAvailableStorages();
    146   for (size_t i = 0; i < storages.size(); ++i) {
    147     StorageInfo::Type type;
    148     if (!StorageInfo::CrackDeviceId(storages[i].device_id(), &type, NULL) ||
    149         (type != StorageInfo::FIXED_MASS_STORAGE &&
    150          type != StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM)) {
    151       continue;
    152     }
    153     base::FilePath path(storages[i].location());
    154     if (ShouldIgnoreScanRoot(path))
    155       continue;
    156     roots.push_back(path);
    157   }
    158 
    159   base::FilePath platform_root = GetPlatformSpecificDefaultScanRoot();
    160   if (!platform_root.empty())
    161     roots.push_back(platform_root);
    162   callback.Run(roots);
    163 }
    164 
    165 }  // namespace
    166 
    167 MediaFolderFinder::WorkerReply::WorkerReply() {}
    168 
    169 MediaFolderFinder::WorkerReply::~WorkerReply() {}
    170 
    171 // The Worker is created on the UI thread, but does all its work on a blocking
    172 // SequencedTaskRunner.
    173 class MediaFolderFinder::Worker {
    174  public:
    175   explicit Worker(const std::vector<base::FilePath>& graylisted_folders);
    176   ~Worker();
    177 
    178   // Scans |path| and return the results.
    179   WorkerReply ScanFolder(const base::FilePath& path);
    180 
    181  private:
    182   void MakeFolderPathsAbsolute();
    183 
    184   bool folder_paths_are_absolute_;
    185   std::vector<base::FilePath> graylisted_folders_;
    186   std::vector<base::FilePath> pruned_folders_;
    187 
    188   scoped_ptr<MediaPathFilter> filter_;
    189 
    190   base::SequenceChecker sequence_checker_;
    191 
    192   DISALLOW_COPY_AND_ASSIGN(Worker);
    193 };
    194 
    195 MediaFolderFinder::Worker::Worker(
    196     const std::vector<base::FilePath>& graylisted_folders)
    197     : folder_paths_are_absolute_(false),
    198       graylisted_folders_(graylisted_folders),
    199       filter_(new MediaPathFilter) {
    200   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    201 
    202   for (size_t i = 0; i < arraysize(kPrunedPaths); ++i) {
    203     base::FilePath path;
    204     if (PathService::Get(kPrunedPaths[i], &path))
    205       pruned_folders_.push_back(path);
    206   }
    207 
    208   sequence_checker_.DetachFromSequence();
    209 }
    210 
    211 MediaFolderFinder::Worker::~Worker() {
    212   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
    213 }
    214 
    215 MediaFolderFinder::WorkerReply MediaFolderFinder::Worker::ScanFolder(
    216     const base::FilePath& path) {
    217   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
    218   CHECK(IsValidScanPath(path));
    219 
    220   if (!folder_paths_are_absolute_)
    221     MakeFolderPathsAbsolute();
    222 
    223   WorkerReply reply;
    224   bool folder_meets_size_requirement = false;
    225   bool is_graylisted_folder = false;
    226   base::FilePath abspath = base::MakeAbsoluteFilePath(path);
    227   if (abspath.empty())
    228     return reply;
    229 
    230   for (size_t i = 0; i < graylisted_folders_.size(); ++i) {
    231     if (abspath == graylisted_folders_[i] ||
    232         abspath.IsParent(graylisted_folders_[i])) {
    233       is_graylisted_folder = true;
    234       break;
    235     }
    236   }
    237 
    238   base::FileEnumerator enumerator(
    239       path,
    240       false, /* recursive? */
    241       base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES
    242 #if defined(OS_POSIX)
    243       | base::FileEnumerator::SHOW_SYM_LINKS  // show symlinks, not follow.
    244 #endif
    245       );  // NOLINT
    246   while (!enumerator.Next().empty()) {
    247     base::FileEnumerator::FileInfo file_info = enumerator.GetInfo();
    248     base::FilePath full_path = path.Append(file_info.GetName());
    249     if (MediaPathFilter::ShouldSkip(full_path))
    250       continue;
    251 
    252     // Enumerating a directory.
    253     if (file_info.IsDirectory()) {
    254       bool is_pruned_folder = false;
    255       base::FilePath abs_full_path = base::MakeAbsoluteFilePath(full_path);
    256       if (abs_full_path.empty())
    257         continue;
    258       for (size_t i = 0; i < pruned_folders_.size(); ++i) {
    259         if (abs_full_path == pruned_folders_[i]) {
    260           is_pruned_folder = true;
    261           break;
    262         }
    263       }
    264 
    265       if (!is_pruned_folder)
    266         reply.new_folders.push_back(full_path);
    267       continue;
    268     }
    269 
    270     // Enumerating a file.
    271     //
    272     // Do not include scan results for graylisted folders.
    273     if (is_graylisted_folder)
    274       continue;
    275 
    276     MediaGalleryScanFileType type = filter_->GetType(full_path);
    277     if (type == MEDIA_GALLERY_SCAN_FILE_TYPE_UNKNOWN)
    278       continue;
    279 
    280     CountScanResult(type, &reply.scan_result);
    281     if (!folder_meets_size_requirement) {
    282       folder_meets_size_requirement =
    283           FileMeetsSizeRequirement(type, file_info.GetSize());
    284     }
    285   }
    286   // Make sure there is at least 1 file above a size threshold.
    287   if (!folder_meets_size_requirement)
    288     reply.scan_result = MediaGalleryScanResult();
    289   return reply;
    290 }
    291 
    292 void MediaFolderFinder::Worker::MakeFolderPathsAbsolute() {
    293   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
    294   DCHECK(!folder_paths_are_absolute_);
    295   folder_paths_are_absolute_ = true;
    296 
    297   std::vector<base::FilePath> abs_paths;
    298   for (size_t i = 0; i < graylisted_folders_.size(); ++i) {
    299     base::FilePath path = base::MakeAbsoluteFilePath(graylisted_folders_[i]);
    300     if (!path.empty())
    301       abs_paths.push_back(path);
    302   }
    303   graylisted_folders_ = abs_paths;
    304   abs_paths.clear();
    305   for (size_t i = 0; i < pruned_folders_.size(); ++i) {
    306     base::FilePath path = base::MakeAbsoluteFilePath(pruned_folders_[i]);
    307     if (!path.empty())
    308       abs_paths.push_back(path);
    309   }
    310   pruned_folders_ = abs_paths;
    311 }
    312 
    313 MediaFolderFinder::MediaFolderFinder(
    314     const MediaFolderFinderResultsCallback& callback)
    315     : results_callback_(callback),
    316       graylisted_folders_(
    317           extensions::file_system_api::GetGrayListedDirectories()),
    318       scan_state_(SCAN_STATE_NOT_STARTED),
    319       worker_(new Worker(graylisted_folders_)),
    320       has_roots_for_testing_(false),
    321       weak_factory_(this) {
    322   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    323 
    324   base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
    325   worker_task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken());
    326 }
    327 
    328 MediaFolderFinder::~MediaFolderFinder() {
    329   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    330 
    331   worker_task_runner_->DeleteSoon(FROM_HERE, worker_);
    332 
    333   if (scan_state_ == SCAN_STATE_FINISHED)
    334     return;
    335 
    336   MediaFolderFinderResults empty_results;
    337   results_callback_.Run(false /* success? */, empty_results);
    338 }
    339 
    340 void MediaFolderFinder::StartScan() {
    341   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    342 
    343   if (scan_state_ != SCAN_STATE_NOT_STARTED)
    344     return;
    345 
    346   scan_state_ = SCAN_STATE_STARTED;
    347   GetDefaultScanRoots(
    348       base::Bind(&MediaFolderFinder::OnInitialized, weak_factory_.GetWeakPtr()),
    349       has_roots_for_testing_,
    350       roots_for_testing_);
    351 }
    352 
    353 const std::vector<base::FilePath>&
    354 MediaFolderFinder::graylisted_folders() const {
    355   return graylisted_folders_;
    356 }
    357 
    358 void MediaFolderFinder::SetRootsForTesting(
    359     const std::vector<base::FilePath>& roots) {
    360   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    361   DCHECK_EQ(SCAN_STATE_NOT_STARTED, scan_state_);
    362 
    363   has_roots_for_testing_ = true;
    364   roots_for_testing_ = roots;
    365 }
    366 
    367 void MediaFolderFinder::OnInitialized(
    368     const std::vector<base::FilePath>& roots) {
    369   DCHECK_EQ(SCAN_STATE_STARTED, scan_state_);
    370 
    371   std::set<base::FilePath> valid_roots;
    372   for (size_t i = 0; i < roots.size(); ++i) {
    373     // Skip if |path| is invalid or redundant.
    374     const base::FilePath& path = roots[i];
    375     if (!IsValidScanPath(path))
    376       continue;
    377     if (ContainsKey(valid_roots, path))
    378       continue;
    379 
    380     // Check for overlap.
    381     bool valid_roots_contains_path = false;
    382     std::vector<base::FilePath> overlapping_paths_to_remove;
    383     for (std::set<base::FilePath>::iterator it = valid_roots.begin();
    384          it != valid_roots.end(); ++it) {
    385       if (it->IsParent(path)) {
    386         valid_roots_contains_path = true;
    387         break;
    388       }
    389       const base::FilePath& other_path = *it;
    390       if (path.IsParent(other_path))
    391         overlapping_paths_to_remove.push_back(other_path);
    392     }
    393     if (valid_roots_contains_path)
    394       continue;
    395     // Remove anything |path| overlaps from |valid_roots|.
    396     for (size_t i = 0; i < overlapping_paths_to_remove.size(); ++i)
    397       valid_roots.erase(overlapping_paths_to_remove[i]);
    398 
    399     valid_roots.insert(path);
    400   }
    401 
    402   std::copy(valid_roots.begin(), valid_roots.end(),
    403             std::back_inserter(folders_to_scan_));
    404   ScanFolder();
    405 }
    406 
    407 void MediaFolderFinder::ScanFolder() {
    408   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    409   DCHECK_EQ(SCAN_STATE_STARTED, scan_state_);
    410 
    411   if (folders_to_scan_.empty()) {
    412     scan_state_ = SCAN_STATE_FINISHED;
    413     results_callback_.Run(true /* success? */, results_);
    414     return;
    415   }
    416 
    417   base::FilePath folder_to_scan = folders_to_scan_.back();
    418   folders_to_scan_.pop_back();
    419   base::PostTaskAndReplyWithResult(
    420       worker_task_runner_, FROM_HERE,
    421       base::Bind(&Worker::ScanFolder,
    422                  base::Unretained(worker_),
    423                  folder_to_scan),
    424       base::Bind(&MediaFolderFinder::GotScanResults,
    425                  weak_factory_.GetWeakPtr(),
    426                  folder_to_scan));
    427 }
    428 
    429 void MediaFolderFinder::GotScanResults(const base::FilePath& path,
    430                                        const WorkerReply& reply) {
    431   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    432   DCHECK_EQ(SCAN_STATE_STARTED, scan_state_);
    433   DCHECK(!path.empty());
    434   CHECK(!ContainsKey(results_, path));
    435 
    436   if (!IsEmptyScanResult(reply.scan_result))
    437     results_[path] = reply.scan_result;
    438 
    439   // Push new folders to the |folders_to_scan_| in reverse order.
    440   std::copy(reply.new_folders.rbegin(), reply.new_folders.rend(),
    441             std::back_inserter(folders_to_scan_));
    442 
    443   ScanFolder();
    444 }
    445