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/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" 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_.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)); 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