• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IsValidScanPath(const base::FilePath & path)61 bool IsValidScanPath(const base::FilePath& path) {
62   return !path.empty() && path.IsAbsolute();
63 }
64 
CountScanResult(MediaGalleryScanFileType type,MediaGalleryScanResult * scan_result)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 
FileMeetsSizeRequirement(MediaGalleryScanFileType type,int64 size)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.
ShouldIgnoreScanRoot(const base::FilePath & path)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.
GetPlatformSpecificDefaultScanRoot()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.
GetDefaultScanRoots(const DefaultScanRootsCallback & callback,bool has_override,const std::vector<base::FilePath> & override_paths)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 
WorkerReply()167 MediaFolderFinder::WorkerReply::WorkerReply() {}
168 
~WorkerReply()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 
Worker(const std::vector<base::FilePath> & graylisted_folders)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 
~Worker()211 MediaFolderFinder::Worker::~Worker() {
212   DCHECK(sequence_checker_.CalledOnValidSequencedThread());
213 }
214 
ScanFolder(const base::FilePath & path)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 
MakeFolderPathsAbsolute()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 
MediaFolderFinder(const MediaFolderFinderResultsCallback & callback)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 
~MediaFolderFinder()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 
StartScan()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>&
graylisted_folders() const354 MediaFolderFinder::graylisted_folders() const {
355   return graylisted_folders_;
356 }
357 
SetRootsForTesting(const std::vector<base::FilePath> & roots)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 
OnInitialized(const std::vector<base::FilePath> & roots)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 
ScanFolder()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 
GotScanResults(const base::FilePath & path,const WorkerReply & reply)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