Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / media_galleries / media_folder_finder.cc
blob26ec50eb3a0fb27aec810a2862416c9d71fa97b4
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"
7 #include <algorithm>
8 #include <set>
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"
27 #endif
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;
36 namespace {
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[] = {
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
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)
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;
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 (base::StartsWith(path.value(), "/media", base::CompareCase::SENSITIVE) ||
102 base::StartsWith(path.value(), "/mnt", base::CompareCase::SENSITIVE)) {
103 return false;
105 return true;
106 #elif defined(OS_WIN)
107 return false;
108 #else
109 NOTIMPLEMENTED();
110 return false;
111 #endif
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;
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);
136 if (has_override) {
137 callback.Run(override_paths);
138 return;
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)) {
151 continue;
153 base::FilePath path(storages[i].location());
154 if (ShouldIgnoreScanRoot(path))
155 continue;
156 roots.push_back(path);
159 base::FilePath platform_root = GetPlatformSpecificDefaultScanRoot();
160 if (!platform_root.empty())
161 roots.push_back(platform_root);
162 callback.Run(roots);
165 } // namespace
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 {
174 public:
175 explicit Worker(const std::vector<base::FilePath>& graylisted_folders);
176 ~Worker();
178 // Scans |path| and return the results.
179 WorkerReply ScanFolder(const base::FilePath& path);
181 private:
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) {
203 base::FilePath path;
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();
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;
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;
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;
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;
265 if (!is_pruned_folder)
266 reply.new_folders.push_back(full_path);
267 continue;
270 // Enumerating a file.
272 // Do not include scan results for graylisted folders.
273 if (is_graylisted_folder)
274 continue;
276 MediaGalleryScanFileType type = filter_->GetType(full_path);
277 if (type == MEDIA_GALLERY_SCAN_FILE_TYPE_UNKNOWN)
278 continue;
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();
289 return reply;
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]);
300 if (!path.empty())
301 abs_paths.push_back(path);
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);
310 pruned_folders_ = abs_paths;
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);
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)
334 return;
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)
344 return;
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_);
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))
376 continue;
377 if (ContainsKey(valid_roots, path))
378 continue;
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;
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)
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]);
399 valid_roots.insert(path);
402 std::copy(valid_roots.begin(), valid_roots.end(),
403 std::back_inserter(folders_to_scan_));
404 ScanFolder();
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_);
414 return;
417 base::FilePath folder_to_scan = folders_to_scan_.back();
418 folders_to_scan_.pop_back();
419 base::PostTaskAndReplyWithResult(
420 worker_task_runner_.get(),
421 FROM_HERE,
422 base::Bind(
423 &Worker::ScanFolder, base::Unretained(worker_), folder_to_scan),
424 base::Bind(&MediaFolderFinder::GotScanResults,
425 weak_factory_.GetWeakPtr(),
426 folder_to_scan));
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_));
443 ScanFolder();