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.
5 #include "chrome/browser/media_galleries/media_folder_finder.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/files/file_util.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"
24 #if defined(OS_CHROMEOS)
25 #include "chrome/common/chrome_paths.h"
26 #include "chromeos/dbus/cros_disks_client.h"
29 using storage_monitor::StorageInfo
;
30 using storage_monitor::StorageMonitor
;
32 typedef base::Callback
<void(const std::vector
<base::FilePath
>& /*roots*/)>
33 DefaultScanRootsCallback
;
34 using content::BrowserThread
;
38 const int64 kMinimumImageSize
= 200 * 1024; // 200 KB
39 const int64 kMinimumAudioSize
= 500 * 1024; // 500 KB
40 const int64 kMinimumVideoSize
= 1024 * 1024; // 1 MB
42 const int kPrunedPaths
[] = {
44 base::DIR_IE_INTERNET_CACHE
,
45 base::DIR_PROGRAM_FILES
,
46 base::DIR_PROGRAM_FILESX86
,
49 #if defined(OS_MACOSX) && !defined(OS_IOS)
50 chrome::DIR_USER_APPLICATIONS
,
51 chrome::DIR_USER_LIBRARY
,
56 #if defined(OS_WIN) || defined(OS_LINUX)
61 bool IsValidScanPath(const base::FilePath
& path
) {
62 return !path
.empty() && path
.IsAbsolute();
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;
75 bool FileMeetsSizeRequirement(MediaGalleryScanFileType type
, int64 size
) {
76 if (type
& MEDIA_GALLERY_SCAN_FILE_TYPE_IMAGE
)
77 if (size
>= kMinimumImageSize
)
79 if (type
& MEDIA_GALLERY_SCAN_FILE_TYPE_AUDIO
)
80 if (size
>= kMinimumAudioSize
)
82 if (type
& MEDIA_GALLERY_SCAN_FILE_TYPE_VIDEO
)
83 if (size
>= kMinimumVideoSize
)
88 // Return true if |path| should not be considered as the starting point for a
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 (base::StartsWith(path
.value(), "/media", base::CompareCase::SENSITIVE
) ||
102 base::StartsWith(path
.value(), "/mnt", base::CompareCase::SENSITIVE
)) {
106 #elif defined(OS_WIN)
114 // Return a location that is likely to have user data to scan, if any.
115 base::FilePath
GetPlatformSpecificDefaultScanRoot() {
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)
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
,
133 const std::vector
<base::FilePath
>& override_paths
) {
134 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
137 callback
.Run(override_paths
);
141 StorageMonitor
* monitor
= StorageMonitor::GetInstance();
142 DCHECK(monitor
->IsInitialized());
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
)) {
153 base::FilePath
path(storages
[i
].location());
154 if (ShouldIgnoreScanRoot(path
))
156 roots
.push_back(path
);
159 base::FilePath platform_root
= GetPlatformSpecificDefaultScanRoot();
160 if (!platform_root
.empty())
161 roots
.push_back(platform_root
);
167 MediaFolderFinder::WorkerReply::WorkerReply() {}
169 MediaFolderFinder::WorkerReply::~WorkerReply() {}
171 // The Worker is created on the UI thread, but does all its work on a blocking
172 // SequencedTaskRunner.
173 class MediaFolderFinder::Worker
{
175 explicit Worker(const std::vector
<base::FilePath
>& graylisted_folders
);
178 // Scans |path| and return the results.
179 WorkerReply
ScanFolder(const base::FilePath
& path
);
182 void MakeFolderPathsAbsolute();
184 bool folder_paths_are_absolute_
;
185 std::vector
<base::FilePath
> graylisted_folders_
;
186 std::vector
<base::FilePath
> pruned_folders_
;
188 scoped_ptr
<MediaPathFilter
> filter_
;
190 base::SequenceChecker sequence_checker_
;
192 DISALLOW_COPY_AND_ASSIGN(Worker
);
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
);
202 for (size_t i
= 0; i
< arraysize(kPrunedPaths
); ++i
) {
204 if (PathService::Get(kPrunedPaths
[i
], &path
))
205 pruned_folders_
.push_back(path
);
208 sequence_checker_
.DetachFromSequence();
211 MediaFolderFinder::Worker::~Worker() {
212 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
215 MediaFolderFinder::WorkerReply
MediaFolderFinder::Worker::ScanFolder(
216 const base::FilePath
& path
) {
217 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
218 CHECK(IsValidScanPath(path
));
220 if (!folder_paths_are_absolute_
)
221 MakeFolderPathsAbsolute();
224 bool folder_meets_size_requirement
= false;
225 bool is_graylisted_folder
= false;
226 base::FilePath abspath
= base::MakeAbsoluteFilePath(path
);
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;
238 base::FileEnumerator
enumerator(
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.
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
))
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())
258 for (size_t i
= 0; i
< pruned_folders_
.size(); ++i
) {
259 if (abs_full_path
== pruned_folders_
[i
]) {
260 is_pruned_folder
= true;
265 if (!is_pruned_folder
)
266 reply
.new_folders
.push_back(full_path
);
270 // Enumerating a file.
272 // Do not include scan results for graylisted folders.
273 if (is_graylisted_folder
)
276 MediaGalleryScanFileType type
= filter_
->GetType(full_path
);
277 if (type
== MEDIA_GALLERY_SCAN_FILE_TYPE_UNKNOWN
)
280 CountScanResult(type
, &reply
.scan_result
);
281 if (!folder_meets_size_requirement
) {
282 folder_meets_size_requirement
=
283 FileMeetsSizeRequirement(type
, file_info
.GetSize());
286 // Make sure there is at least 1 file above a size threshold.
287 if (!folder_meets_size_requirement
)
288 reply
.scan_result
= MediaGalleryScanResult();
292 void MediaFolderFinder::Worker::MakeFolderPathsAbsolute() {
293 DCHECK(sequence_checker_
.CalledOnValidSequencedThread());
294 DCHECK(!folder_paths_are_absolute_
);
295 folder_paths_are_absolute_
= true;
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
]);
301 abs_paths
.push_back(path
);
303 graylisted_folders_
= abs_paths
;
305 for (size_t i
= 0; i
< pruned_folders_
.size(); ++i
) {
306 base::FilePath path
= base::MakeAbsoluteFilePath(pruned_folders_
[i
]);
308 abs_paths
.push_back(path
);
310 pruned_folders_
= abs_paths
;
313 MediaFolderFinder::MediaFolderFinder(
314 const MediaFolderFinderResultsCallback
& callback
)
315 : results_callback_(callback
),
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
);
324 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
325 worker_task_runner_
= pool
->GetSequencedTaskRunner(pool
->GetSequenceToken());
328 MediaFolderFinder::~MediaFolderFinder() {
329 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
331 worker_task_runner_
->DeleteSoon(FROM_HERE
, worker_
);
333 if (scan_state_
== SCAN_STATE_FINISHED
)
336 MediaFolderFinderResults empty_results
;
337 results_callback_
.Run(false /* success? */, empty_results
);
340 void MediaFolderFinder::StartScan() {
341 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
343 if (scan_state_
!= SCAN_STATE_NOT_STARTED
)
346 scan_state_
= SCAN_STATE_STARTED
;
348 base::Bind(&MediaFolderFinder::OnInitialized
, weak_factory_
.GetWeakPtr()),
349 has_roots_for_testing_
,
353 const std::vector
<base::FilePath
>&
354 MediaFolderFinder::graylisted_folders() const {
355 return graylisted_folders_
;
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_
);
363 has_roots_for_testing_
= true;
364 roots_for_testing_
= roots
;
367 void MediaFolderFinder::OnInitialized(
368 const std::vector
<base::FilePath
>& roots
) {
369 DCHECK_EQ(SCAN_STATE_STARTED
, scan_state_
);
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
))
377 if (ContainsKey(valid_roots
, path
))
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;
389 const base::FilePath
& other_path
= *it
;
390 if (path
.IsParent(other_path
))
391 overlapping_paths_to_remove
.push_back(other_path
);
393 if (valid_roots_contains_path
)
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
]);
399 valid_roots
.insert(path
);
402 std::copy(valid_roots
.begin(), valid_roots
.end(),
403 std::back_inserter(folders_to_scan_
));
407 void MediaFolderFinder::ScanFolder() {
408 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
409 DCHECK_EQ(SCAN_STATE_STARTED
, scan_state_
);
411 if (folders_to_scan_
.empty()) {
412 scan_state_
= SCAN_STATE_FINISHED
;
413 results_callback_
.Run(true /* success? */, results_
);
417 base::FilePath folder_to_scan
= folders_to_scan_
.back();
418 folders_to_scan_
.pop_back();
419 base::PostTaskAndReplyWithResult(
420 worker_task_runner_
.get(),
423 &Worker::ScanFolder
, base::Unretained(worker_
), folder_to_scan
),
424 base::Bind(&MediaFolderFinder::GotScanResults
,
425 weak_factory_
.GetWeakPtr(),
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
));
436 if (!IsEmptyScanResult(reply
.scan_result
))
437 results_
[path
] = reply
.scan_result
;
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_
));