Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(54)

Side by Side Diff: chrome/browser/media_galleries/media_scan_manager.cc

Issue 1695563002: Media Galleries Partial Deprecation: Remove scan functionality. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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_scan_manager.h"
6
7 #include <stddef.h>
8
9 #include "base/files/file_enumerator.h"
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/media_galleries/media_galleries_preferences.h"
16 #include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
17 #include "chrome/browser/media_galleries/media_scan_manager_observer.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/extensions/api/media_galleries.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "extensions/browser/extension_registry.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/common/extension.h"
24
25 using extensions::ExtensionRegistry;
26
27 namespace media_galleries = extensions::api::media_galleries;
28
29 namespace {
30
31 typedef std::set<std::string /*extension id*/> ScanningExtensionIdSet;
32
33 // When multiple scan results have the same parent, sometimes it makes sense
34 // to combine them into a single scan result at the parent. This constant
35 // governs when that happens; kContainerDirectoryMinimumPercent percent of the
36 // directories in the parent directory must be scan results.
37 const int kContainerDirectoryMinimumPercent = 80;
38
39 // How long after a completed media scan can we provide the cached results.
40 const int kScanResultsExpiryTimeInHours = 24;
41
42 struct LocationInfo {
43 LocationInfo()
44 : pref_id(kInvalidMediaGalleryPrefId),
45 type(MediaGalleryPrefInfo::kInvalidType) {}
46 LocationInfo(MediaGalleryPrefId pref_id, MediaGalleryPrefInfo::Type type,
47 base::FilePath path)
48 : pref_id(pref_id), type(type), path(path) {}
49 // Highest priority comparison by path, next by type (scan result last),
50 // then by pref id (invalid last).
51 bool operator<(const LocationInfo& rhs) const {
52 if (path.value() == rhs.path.value()) {
53 if (type == rhs.type) {
54 return pref_id > rhs.pref_id;
55 }
56 return rhs.type == MediaGalleryPrefInfo::kScanResult;
57 }
58 return path.value() < rhs.path.value();
59 }
60
61 MediaGalleryPrefId pref_id;
62 MediaGalleryPrefInfo::Type type;
63 base::FilePath path;
64 MediaGalleryScanResult file_counts;
65 };
66
67 // Finds new scan results that are shadowed (the same location, or a child) by
68 // existing locations and moves them from |found_folders| to |child_folders|.
69 // Also moves new scan results that are shadowed by other new scan results
70 // to |child_folders|.
71 void PartitionChildScanResults(
72 MediaGalleriesPreferences* preferences,
73 MediaFolderFinder::MediaFolderFinderResults* found_folders,
74 MediaFolderFinder::MediaFolderFinderResults* child_folders) {
75 // Construct a list with everything in it.
76 std::vector<LocationInfo> all_locations;
77 for (MediaFolderFinder::MediaFolderFinderResults::const_iterator it =
78 found_folders->begin(); it != found_folders->end(); ++it) {
79 all_locations.push_back(LocationInfo(kInvalidMediaGalleryPrefId,
80 MediaGalleryPrefInfo::kScanResult,
81 it->first));
82 all_locations.back().file_counts = it->second;
83 }
84 const MediaGalleriesPrefInfoMap& known_galleries =
85 preferences->known_galleries();
86 for (MediaGalleriesPrefInfoMap::const_iterator it = known_galleries.begin();
87 it != known_galleries.end();
88 ++it) {
89 all_locations.push_back(LocationInfo(it->second.pref_id, it->second.type,
90 it->second.AbsolutePath()));
91 }
92 // Sorting on path should put all paths that are prefixes of other paths
93 // next to each other, with the shortest one first.
94 std::sort(all_locations.begin(), all_locations.end());
95
96 size_t previous_parent_index = 0;
97 for (size_t i = 1; i < all_locations.size(); i++) {
98 const LocationInfo& current = all_locations[i];
99 const LocationInfo& previous_parent = all_locations[previous_parent_index];
100 bool is_child = previous_parent.path.IsParent(current.path);
101 if (current.type == MediaGalleryPrefInfo::kScanResult &&
102 current.pref_id == kInvalidMediaGalleryPrefId &&
103 (is_child || previous_parent.path == current.path)) {
104 // Move new scan results that are shadowed.
105 (*child_folders)[current.path] = current.file_counts;
106 found_folders->erase(current.path);
107 } else if (!is_child) {
108 previous_parent_index = i;
109 }
110 }
111 }
112
113 MediaGalleryScanResult SumFilesUnderPath(
114 const base::FilePath& path,
115 const MediaFolderFinder::MediaFolderFinderResults& candidates) {
116 MediaGalleryScanResult results;
117 for (MediaFolderFinder::MediaFolderFinderResults::const_iterator it =
118 candidates.begin(); it != candidates.end(); ++it) {
119 if (it->first == path || path.IsParent(it->first)) {
120 results.audio_count += it->second.audio_count;
121 results.image_count += it->second.image_count;
122 results.video_count += it->second.video_count;
123 }
124 }
125 return results;
126 }
127
128 void AddScanResultsForProfile(
129 MediaGalleriesPreferences* preferences,
130 const MediaFolderFinder::MediaFolderFinderResults& found_folders) {
131 // First, remove any existing scan results where no app has been granted
132 // permission - either it is gone, or is already in the new scan results.
133 // This burns some pref ids, but not at an appreciable rate.
134 MediaGalleryPrefIdSet to_remove;
135 const MediaGalleriesPrefInfoMap& known_galleries =
136 preferences->known_galleries();
137 for (MediaGalleriesPrefInfoMap::const_iterator it = known_galleries.begin();
138 it != known_galleries.end();
139 ++it) {
140 if (it->second.type == MediaGalleryPrefInfo::kScanResult &&
141 !preferences->NonAutoGalleryHasPermission(it->first)) {
142 to_remove.insert(it->first);
143 }
144 }
145 for (MediaGalleryPrefIdSet::const_iterator it = to_remove.begin();
146 it != to_remove.end();
147 ++it) {
148 preferences->EraseGalleryById(*it);
149 }
150
151 MediaFolderFinder::MediaFolderFinderResults child_folders;
152 MediaFolderFinder::MediaFolderFinderResults
153 unique_found_folders(found_folders);
154 PartitionChildScanResults(preferences, &unique_found_folders, &child_folders);
155
156 // Updating prefs while iterating them will invalidate the pointer, so
157 // calculate the changes first and then apply them.
158 std::map<MediaGalleryPrefId, MediaGalleryScanResult> to_update;
159 for (MediaGalleriesPrefInfoMap::const_iterator it = known_galleries.begin();
160 it != known_galleries.end();
161 ++it) {
162 const MediaGalleryPrefInfo& gallery = it->second;
163 if (!gallery.IsBlackListedType()) {
164 MediaGalleryScanResult file_counts =
165 SumFilesUnderPath(gallery.AbsolutePath(), child_folders);
166 if (gallery.audio_count != file_counts.audio_count ||
167 gallery.image_count != file_counts.image_count ||
168 gallery.video_count != file_counts.video_count) {
169 to_update[it->first] = file_counts;
170 }
171 }
172 }
173
174 for (std::map<MediaGalleryPrefId,
175 MediaGalleryScanResult>::const_iterator it = to_update.begin();
176 it != to_update.end();
177 ++it) {
178 const MediaGalleryPrefInfo& gallery =
179 preferences->known_galleries().find(it->first)->second;
180 preferences->AddGallery(gallery.device_id, gallery.path, gallery.type,
181 gallery.volume_label, gallery.vendor_name,
182 gallery.model_name, gallery.total_size_in_bytes,
183 gallery.last_attach_time,
184 it->second.audio_count,
185 it->second.image_count,
186 it->second.video_count);
187 }
188
189 // Add new scan results.
190 for (MediaFolderFinder::MediaFolderFinderResults::const_iterator it =
191 unique_found_folders.begin();
192 it != unique_found_folders.end();
193 ++it) {
194 MediaGalleryScanResult file_counts =
195 SumFilesUnderPath(it->first, child_folders);
196 // The top level scan result is not in |child_folders|. Add it in as well.
197 file_counts.audio_count += it->second.audio_count;
198 file_counts.image_count += it->second.image_count;
199 file_counts.video_count += it->second.video_count;
200
201 MediaGalleryPrefInfo gallery;
202 bool existing = preferences->LookUpGalleryByPath(it->first, &gallery);
203 DCHECK(!existing);
204 preferences->AddGallery(gallery.device_id, gallery.path,
205 MediaGalleryPrefInfo::kScanResult,
206 gallery.volume_label, gallery.vendor_name,
207 gallery.model_name, gallery.total_size_in_bytes,
208 gallery.last_attach_time, file_counts.audio_count,
209 file_counts.image_count, file_counts.video_count);
210 }
211 UMA_HISTOGRAM_COUNTS_10000("MediaGalleries.ScanGalleriesPopulated",
212 unique_found_folders.size() + to_update.size());
213 }
214
215 int CountScanResultsForExtension(MediaGalleriesPreferences* preferences,
216 const extensions::Extension* extension,
217 MediaGalleryScanResult* file_counts) {
218 int gallery_count = 0;
219
220 MediaGalleryPrefIdSet permitted_galleries =
221 preferences->GalleriesForExtension(*extension);
222 const MediaGalleriesPrefInfoMap& known_galleries =
223 preferences->known_galleries();
224 for (MediaGalleriesPrefInfoMap::const_iterator it = known_galleries.begin();
225 it != known_galleries.end();
226 ++it) {
227 if (it->second.type == MediaGalleryPrefInfo::kScanResult &&
228 !ContainsKey(permitted_galleries, it->first)) {
229 gallery_count++;
230 file_counts->audio_count += it->second.audio_count;
231 file_counts->image_count += it->second.image_count;
232 file_counts->video_count += it->second.video_count;
233 }
234 }
235 return gallery_count;
236 }
237
238 int CountDirectoryEntries(const base::FilePath& path) {
239 base::FileEnumerator dir_counter(
240 path, false /*recursive*/, base::FileEnumerator::DIRECTORIES);
241 int count = 0;
242 base::FileEnumerator::FileInfo info;
243 for (base::FilePath name = dir_counter.Next(); !name.empty();
244 name = dir_counter.Next()) {
245 if (!base::IsLink(name))
246 ++count;
247 }
248 return count;
249 }
250
251 struct ContainerCount {
252 int seen_count, entries_count;
253 bool is_qualified;
254
255 ContainerCount() : seen_count(0), entries_count(-1), is_qualified(false) {}
256 };
257
258 typedef std::map<base::FilePath, ContainerCount> ContainerCandidates;
259
260 } // namespace
261
262 MediaScanManager::MediaScanManager()
263 : scoped_extension_registry_observer_(this),
264 weak_factory_(this) {
265 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
266 }
267
268 MediaScanManager::~MediaScanManager() {
269 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
270 }
271
272 void MediaScanManager::AddObserver(Profile* profile,
273 MediaScanManagerObserver* observer) {
274 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
275 DCHECK(!ContainsKey(observers_, profile));
276 observers_[profile].observer = observer;
277 }
278
279 void MediaScanManager::RemoveObserver(Profile* profile) {
280 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
281 bool scan_in_progress = ScanInProgress();
282 observers_.erase(profile);
283 DCHECK_EQ(scan_in_progress, ScanInProgress());
284 }
285
286 void MediaScanManager::CancelScansForProfile(Profile* profile) {
287 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
288 observers_[profile].scanning_extensions.clear();
289
290 if (!ScanInProgress())
291 folder_finder_.reset();
292 }
293
294 void MediaScanManager::StartScan(Profile* profile,
295 const extensions::Extension* extension,
296 bool user_gesture) {
297 DCHECK(extension);
298 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
299
300 ScanObserverMap::iterator scans_for_profile = observers_.find(profile);
301 // We expect that an MediaScanManagerObserver has already been registered.
302 DCHECK(scans_for_profile != observers_.end());
303 bool scan_in_progress = ScanInProgress();
304 // Ignore requests for extensions that are already scanning.
305 ScanningExtensionIdSet* scanning_extensions;
306 scanning_extensions = &scans_for_profile->second.scanning_extensions;
307 if (scan_in_progress && ContainsKey(*scanning_extensions, extension->id()))
308 return;
309
310 // Provide cached result if there is not already a scan in progress,
311 // there is no user gesture, and the previous results are unexpired.
312 MediaGalleriesPreferences* preferences =
313 MediaGalleriesPreferencesFactory::GetForProfile(profile);
314 base::TimeDelta time_since_last_scan =
315 base::Time::Now() - preferences->GetLastScanCompletionTime();
316 if (!scan_in_progress && !user_gesture && time_since_last_scan <
317 base::TimeDelta::FromHours(kScanResultsExpiryTimeInHours)) {
318 MediaGalleryScanResult file_counts;
319 int gallery_count =
320 CountScanResultsForExtension(preferences, extension, &file_counts);
321 scans_for_profile->second.observer->OnScanStarted(extension->id());
322 scans_for_profile->second.observer->OnScanFinished(extension->id(),
323 gallery_count,
324 file_counts);
325 return;
326 }
327
328 // On first scan for the |profile|, register to listen for extension unload.
329 if (scanning_extensions->empty())
330 scoped_extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
331
332 scanning_extensions->insert(extension->id());
333 scans_for_profile->second.observer->OnScanStarted(extension->id());
334
335 if (folder_finder_)
336 return;
337
338 MediaFolderFinder::MediaFolderFinderResultsCallback callback =
339 base::Bind(&MediaScanManager::OnScanCompleted,
340 weak_factory_.GetWeakPtr());
341 if (testing_folder_finder_factory_.is_null()) {
342 folder_finder_.reset(new MediaFolderFinder(callback));
343 } else {
344 folder_finder_.reset(testing_folder_finder_factory_.Run(callback));
345 }
346 scan_start_time_ = base::Time::Now();
347 folder_finder_->StartScan();
348 }
349
350 void MediaScanManager::CancelScan(Profile* profile,
351 const extensions::Extension* extension) {
352 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
353
354 // Erases the logical scan if found, early exit otherwise.
355 ScanObserverMap::iterator scans_for_profile = observers_.find(profile);
356 if (scans_for_profile == observers_.end() ||
357 !scans_for_profile->second.scanning_extensions.erase(extension->id())) {
358 return;
359 }
360
361 scans_for_profile->second.observer->OnScanCancelled(extension->id());
362
363 // No more scanning extensions for |profile|, so stop listening for unloads.
364 if (scans_for_profile->second.scanning_extensions.empty())
365 scoped_extension_registry_observer_.Remove(ExtensionRegistry::Get(profile));
366
367 if (!ScanInProgress()) {
368 folder_finder_.reset();
369 DCHECK(!scan_start_time_.is_null());
370 UMA_HISTOGRAM_LONG_TIMES("MediaGalleries.ScanCancelTime",
371 base::Time::Now() - scan_start_time_);
372 scan_start_time_ = base::Time();
373 }
374 }
375
376 void MediaScanManager::SetMediaFolderFinderFactory(
377 const MediaFolderFinderFactory& factory) {
378 testing_folder_finder_factory_ = factory;
379 }
380
381 // A single directory may contain many folders with media in them, without
382 // containing any media itself. In fact, the primary purpose of that directory
383 // may be to contain media directories. This function tries to find those
384 // container directories.
385 MediaFolderFinder::MediaFolderFinderResults
386 MediaScanManager::FindContainerScanResults(
387 const MediaFolderFinder::MediaFolderFinderResults& found_folders,
388 const std::vector<base::FilePath>& sensitive_locations) {
389 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
390 std::vector<base::FilePath> abs_sensitive_locations;
391 for (size_t i = 0; i < sensitive_locations.size(); ++i) {
392 base::FilePath path = base::MakeAbsoluteFilePath(sensitive_locations[i]);
393 if (!path.empty())
394 abs_sensitive_locations.push_back(path);
395 }
396 // Recursively find parent directories with majority of media directories,
397 // or container directories.
398 // |candidates| keeps track of directories which might have enough
399 // such directories to have us return them.
400 typedef std::map<base::FilePath, ContainerCount> ContainerCandidates;
401 ContainerCandidates candidates;
402 for (MediaFolderFinder::MediaFolderFinderResults::const_iterator it =
403 found_folders.begin();
404 it != found_folders.end();
405 ++it) {
406 base::FilePath child_directory = it->first;
407 base::FilePath parent_directory = child_directory.DirName();
408
409 // Parent of root is root.
410 while (!parent_directory.empty() && child_directory != parent_directory) {
411 // Skip sensitive folders and their ancestors.
412 base::FilePath abs_parent_directory =
413 base::MakeAbsoluteFilePath(parent_directory);
414 if (abs_parent_directory.empty())
415 break;
416 bool is_sensitive = false;
417 for (size_t i = 0; i < abs_sensitive_locations.size(); ++i) {
418 if (abs_parent_directory == abs_sensitive_locations[i] ||
419 abs_parent_directory.IsParent(abs_sensitive_locations[i])) {
420 is_sensitive = true;
421 break;
422 }
423 }
424 if (is_sensitive)
425 break;
426
427 // Don't bother with ones we already have.
428 if (found_folders.find(parent_directory) != found_folders.end())
429 continue;
430
431 ContainerCandidates::iterator parent_it =
432 candidates.find(parent_directory);
433 if (parent_it == candidates.end()) {
434 ContainerCount count;
435 count.seen_count = 1;
436 count.entries_count = CountDirectoryEntries(parent_directory);
437 parent_it =
438 candidates.insert(std::make_pair(parent_directory, count)).first;
439 } else {
440 ++candidates[parent_directory].seen_count;
441 }
442 // If previously sufficient, or not sufficient, bail.
443 if (parent_it->second.is_qualified ||
444 parent_it->second.seen_count * 100 / parent_it->second.entries_count <
445 kContainerDirectoryMinimumPercent) {
446 break;
447 }
448 // Otherwise, mark qualified and check parent.
449 parent_it->second.is_qualified = true;
450 child_directory = parent_directory;
451 parent_directory = child_directory.DirName();
452 }
453 }
454 MediaFolderFinder::MediaFolderFinderResults result;
455 // Copy and return worthy results.
456 for (ContainerCandidates::const_iterator it = candidates.begin();
457 it != candidates.end();
458 ++it) {
459 if (it->second.is_qualified && it->second.seen_count >= 2)
460 result[it->first] = MediaGalleryScanResult();
461 }
462 return result;
463 }
464
465 MediaScanManager::ScanObservers::ScanObservers() : observer(NULL) {}
466 MediaScanManager::ScanObservers::ScanObservers(const ScanObservers& other) =
467 default;
468 MediaScanManager::ScanObservers::~ScanObservers() {}
469
470 void MediaScanManager::OnExtensionUnloaded(
471 content::BrowserContext* browser_context,
472 const extensions::Extension* extension,
473 extensions::UnloadedExtensionInfo::Reason reason) {
474 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
475 CancelScan(Profile::FromBrowserContext(browser_context), extension);
476 }
477
478 bool MediaScanManager::ScanInProgress() const {
479 for (ScanObserverMap::const_iterator it = observers_.begin();
480 it != observers_.end();
481 ++it) {
482 if (!it->second.scanning_extensions.empty())
483 return true;
484 }
485 return false;
486 }
487
488 void MediaScanManager::OnScanCompleted(
489 bool success,
490 const MediaFolderFinder::MediaFolderFinderResults& found_folders) {
491 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
492 if (!folder_finder_ || !success) {
493 folder_finder_.reset();
494 return;
495 }
496
497 UMA_HISTOGRAM_COUNTS_10000("MediaGalleries.ScanDirectoriesFound",
498 found_folders.size());
499 DCHECK(!scan_start_time_.is_null());
500 UMA_HISTOGRAM_LONG_TIMES("MediaGalleries.ScanFinishedTime",
501 base::Time::Now() - scan_start_time_);
502 scan_start_time_ = base::Time();
503
504 content::BrowserThread::PostTaskAndReplyWithResult(
505 content::BrowserThread::FILE, FROM_HERE,
506 base::Bind(FindContainerScanResults,
507 found_folders,
508 folder_finder_->graylisted_folders()),
509 base::Bind(&MediaScanManager::OnFoundContainerDirectories,
510 weak_factory_.GetWeakPtr(),
511 found_folders));
512 }
513
514 void MediaScanManager::OnFoundContainerDirectories(
515 const MediaFolderFinder::MediaFolderFinderResults& found_folders,
516 const MediaFolderFinder::MediaFolderFinderResults& container_folders) {
517 MediaFolderFinder::MediaFolderFinderResults folders;
518 folders.insert(found_folders.begin(), found_folders.end());
519 folders.insert(container_folders.begin(), container_folders.end());
520
521 for (ScanObserverMap::iterator scans_for_profile = observers_.begin();
522 scans_for_profile != observers_.end();
523 ++scans_for_profile) {
524 if (scans_for_profile->second.scanning_extensions.empty())
525 continue;
526 Profile* profile = scans_for_profile->first;
527 MediaGalleriesPreferences* preferences =
528 MediaGalleriesPreferencesFactory::GetForProfile(profile);
529 ExtensionService* extension_service =
530 extensions::ExtensionSystem::Get(profile)->extension_service();
531 if (!extension_service)
532 continue;
533
534 AddScanResultsForProfile(preferences, folders);
535
536 ScanningExtensionIdSet* scanning_extensions =
537 &scans_for_profile->second.scanning_extensions;
538 for (ScanningExtensionIdSet::const_iterator extension_id_it =
539 scanning_extensions->begin();
540 extension_id_it != scanning_extensions->end();
541 ++extension_id_it) {
542 const extensions::Extension* extension =
543 extension_service->GetExtensionById(*extension_id_it, false);
544 if (extension) {
545 MediaGalleryScanResult file_counts;
546 int gallery_count = CountScanResultsForExtension(preferences, extension,
547 &file_counts);
548 scans_for_profile->second.observer->OnScanFinished(*extension_id_it,
549 gallery_count,
550 file_counts);
551 }
552 }
553 scanning_extensions->clear();
554 preferences->SetLastScanCompletionTime(base::Time::Now());
555 }
556 scoped_extension_registry_observer_.RemoveAll();
557 folder_finder_.reset();
558 }
OLDNEW
« no previous file with comments | « chrome/browser/media_galleries/media_scan_manager.h ('k') | chrome/browser/media_galleries/media_scan_manager_observer.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698