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