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