1 // Copyright (c) 2012 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_galleries_preferences.h"
7 #include "base/base_paths_posix.h"
8 #include "base/callback.h"
9 #include "base/i18n/time_formatting.h"
10 #include "base/path_service.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/extensions/api/media_galleries_private/media_galleries_private_api.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/media_galleries/fileapi/iapps_finder.h"
22 #include "chrome/browser/media_galleries/fileapi/picasa_finder.h"
23 #include "chrome/browser/media_galleries/media_file_system_registry.h"
24 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "chrome/common/extensions/permissions/media_galleries_permission.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/storage_monitor/media_storage_util.h"
30 #include "components/storage_monitor/storage_monitor.h"
31 #include "components/user_prefs/pref_registry_syncable.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "extensions/browser/extension_system.h"
34 #include "extensions/browser/pref_names.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/extension_set.h"
37 #include "extensions/common/permissions/api_permission.h"
38 #include "extensions/common/permissions/permissions_data.h"
39 #include "grit/generated_resources.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/base/text/bytes_formatting.h"
43 using base::DictionaryValue;
44 using base::ListValue;
45 using extensions::ExtensionPrefs;
49 // Pref key for the list of media gallery permissions.
50 const char kMediaGalleriesPermissions[] = "media_galleries_permissions";
51 // Pref key for Media Gallery ID.
52 const char kMediaGalleryIdKey[] = "id";
53 // Pref key for Media Gallery Permission Value.
54 const char kMediaGalleryHasPermissionKey[] = "has_permission";
56 const char kMediaGalleriesDeviceIdKey[] = "deviceId";
57 const char kMediaGalleriesDisplayNameKey[] = "displayName";
58 const char kMediaGalleriesPathKey[] = "path";
59 const char kMediaGalleriesPrefIdKey[] = "prefId";
60 const char kMediaGalleriesTypeKey[] = "type";
61 const char kMediaGalleriesVolumeLabelKey[] = "volumeLabel";
62 const char kMediaGalleriesVendorNameKey[] = "vendorName";
63 const char kMediaGalleriesModelNameKey[] = "modelName";
64 const char kMediaGalleriesSizeKey[] = "totalSize";
65 const char kMediaGalleriesLastAttachTimeKey[] = "lastAttachTime";
66 const char kMediaGalleriesScanAudioCountKey[] = "audioCount";
67 const char kMediaGalleriesScanImageCountKey[] = "imageCount";
68 const char kMediaGalleriesScanVideoCountKey[] = "videoCount";
69 const char kMediaGalleriesPrefsVersionKey[] = "preferencesVersion";
71 const char kMediaGalleriesTypeAutoDetectedValue[] = "autoDetected";
72 const char kMediaGalleriesTypeBlackListedValue[] = "blackListed";
73 const char kMediaGalleriesTypeRemovedScanValue[] = "removedScan";
74 const char kMediaGalleriesTypeScanResultValue[] = "scanResult";
75 const char kMediaGalleriesTypeUserAddedValue[] = "userAdded";
77 const char kIPhotoGalleryName[] = "iPhoto";
78 const char kITunesGalleryName[] = "iTunes";
79 const char kPicasaGalleryName[] = "Picasa";
81 const int kCurrentPrefsVersion = 2;
83 int NumberExtensionsUsingMediaGalleries(Profile* profile) {
87 ExtensionService* extension_service =
88 extensions::ExtensionSystem::Get(profile)->extension_service();
89 if (!extension_service)
92 const extensions::ExtensionSet* extensions = extension_service->extensions();
93 for (extensions::ExtensionSet::const_iterator i = extensions->begin();
94 i != extensions->end(); ++i) {
95 if (extensions::PermissionsData::HasAPIPermission(
96 *i, extensions::APIPermission::kMediaGalleries) ||
97 extensions::PermissionsData::HasAPIPermission(
98 *i, extensions::APIPermission::kMediaGalleriesPrivate)) {
105 bool GetPrefId(const base::DictionaryValue& dict, MediaGalleryPrefId* value) {
106 std::string string_id;
107 if (!dict.GetString(kMediaGalleriesPrefIdKey, &string_id) ||
108 !base::StringToUint64(string_id, value)) {
115 bool GetType(const base::DictionaryValue& dict,
116 MediaGalleryPrefInfo::Type* type) {
117 std::string string_type;
118 if (!dict.GetString(kMediaGalleriesTypeKey, &string_type))
121 if (string_type == kMediaGalleriesTypeUserAddedValue) {
122 *type = MediaGalleryPrefInfo::kUserAdded;
125 if (string_type == kMediaGalleriesTypeAutoDetectedValue) {
126 *type = MediaGalleryPrefInfo::kAutoDetected;
129 if (string_type == kMediaGalleriesTypeBlackListedValue) {
130 *type = MediaGalleryPrefInfo::kBlackListed;
133 if (string_type == kMediaGalleriesTypeScanResultValue) {
134 *type = MediaGalleryPrefInfo::kScanResult;
137 if (string_type == kMediaGalleriesTypeRemovedScanValue) {
138 *type = MediaGalleryPrefInfo::kRemovedScan;
145 const char* TypeToStringValue(MediaGalleryPrefInfo::Type type) {
146 const char* result = NULL;
148 case MediaGalleryPrefInfo::kUserAdded:
149 result = kMediaGalleriesTypeUserAddedValue;
151 case MediaGalleryPrefInfo::kAutoDetected:
152 result = kMediaGalleriesTypeAutoDetectedValue;
154 case MediaGalleryPrefInfo::kBlackListed:
155 result = kMediaGalleriesTypeBlackListedValue;
157 case MediaGalleryPrefInfo::kScanResult:
158 result = kMediaGalleriesTypeScanResultValue;
160 case MediaGalleryPrefInfo::kRemovedScan:
161 result = kMediaGalleriesTypeRemovedScanValue;
170 bool PopulateGalleryPrefInfoFromDictionary(
171 const base::DictionaryValue& dict, MediaGalleryPrefInfo* out_gallery_info) {
172 MediaGalleryPrefId pref_id;
173 base::string16 display_name;
174 std::string device_id;
175 base::FilePath::StringType path;
176 MediaGalleryPrefInfo::Type type = MediaGalleryPrefInfo::kInvalidType;
177 base::string16 volume_label;
178 base::string16 vendor_name;
179 base::string16 model_name;
180 double total_size_in_bytes = 0.0;
181 double last_attach_time = 0.0;
182 bool volume_metadata_valid = false;
186 int prefs_version = 0;
188 if (!GetPrefId(dict, &pref_id) ||
189 !dict.GetString(kMediaGalleriesDeviceIdKey, &device_id) ||
190 !dict.GetString(kMediaGalleriesPathKey, &path) ||
191 !GetType(dict, &type)) {
195 dict.GetString(kMediaGalleriesDisplayNameKey, &display_name);
196 dict.GetInteger(kMediaGalleriesPrefsVersionKey, &prefs_version);
198 if (dict.GetString(kMediaGalleriesVolumeLabelKey, &volume_label) &&
199 dict.GetString(kMediaGalleriesVendorNameKey, &vendor_name) &&
200 dict.GetString(kMediaGalleriesModelNameKey, &model_name) &&
201 dict.GetDouble(kMediaGalleriesSizeKey, &total_size_in_bytes) &&
202 dict.GetDouble(kMediaGalleriesLastAttachTimeKey, &last_attach_time)) {
203 volume_metadata_valid = true;
206 if (dict.GetInteger(kMediaGalleriesScanAudioCountKey, &audio_count) &&
207 dict.GetInteger(kMediaGalleriesScanImageCountKey, &image_count) &&
208 dict.GetInteger(kMediaGalleriesScanVideoCountKey, &video_count)) {
209 out_gallery_info->audio_count = audio_count;
210 out_gallery_info->image_count = image_count;
211 out_gallery_info->video_count = video_count;
213 out_gallery_info->audio_count = 0;
214 out_gallery_info->image_count = 0;
215 out_gallery_info->video_count = 0;
218 out_gallery_info->pref_id = pref_id;
219 out_gallery_info->display_name = display_name;
220 out_gallery_info->device_id = device_id;
221 out_gallery_info->path = base::FilePath(path);
222 out_gallery_info->type = type;
223 out_gallery_info->volume_label = volume_label;
224 out_gallery_info->vendor_name = vendor_name;
225 out_gallery_info->model_name = model_name;
226 out_gallery_info->total_size_in_bytes = total_size_in_bytes;
227 out_gallery_info->last_attach_time =
228 base::Time::FromInternalValue(last_attach_time);
229 out_gallery_info->volume_metadata_valid = volume_metadata_valid;
230 out_gallery_info->prefs_version = prefs_version;
235 base::DictionaryValue* CreateGalleryPrefInfoDictionary(
236 const MediaGalleryPrefInfo& gallery) {
237 base::DictionaryValue* dict = new base::DictionaryValue();
238 dict->SetString(kMediaGalleriesPrefIdKey,
239 base::Uint64ToString(gallery.pref_id));
240 dict->SetString(kMediaGalleriesDeviceIdKey, gallery.device_id);
241 dict->SetString(kMediaGalleriesPathKey, gallery.path.value());
242 dict->SetString(kMediaGalleriesTypeKey, TypeToStringValue(gallery.type));
244 if (gallery.volume_metadata_valid) {
245 dict->SetString(kMediaGalleriesVolumeLabelKey, gallery.volume_label);
246 dict->SetString(kMediaGalleriesVendorNameKey, gallery.vendor_name);
247 dict->SetString(kMediaGalleriesModelNameKey, gallery.model_name);
248 dict->SetDouble(kMediaGalleriesSizeKey, gallery.total_size_in_bytes);
249 dict->SetDouble(kMediaGalleriesLastAttachTimeKey,
250 gallery.last_attach_time.ToInternalValue());
252 dict->SetString(kMediaGalleriesDisplayNameKey, gallery.display_name);
255 if (gallery.audio_count || gallery.image_count || gallery.video_count) {
256 dict->SetInteger(kMediaGalleriesScanAudioCountKey, gallery.audio_count);
257 dict->SetInteger(kMediaGalleriesScanImageCountKey, gallery.image_count);
258 dict->SetInteger(kMediaGalleriesScanVideoCountKey, gallery.video_count);
261 // Version 0 of the prefs format was that the display_name was always
262 // used to show the user-visible name of the gallery. Version 1 means
263 // that there is an optional display_name, and when it is present, it
264 // overrides the name that would be built from the volume metadata, path,
265 // or whatever other data. So if we see a display_name with version 0, it
266 // means it may be overwritten simply by getting new volume metadata.
267 // A display_name with version 1 should not be overwritten.
268 dict->SetInteger(kMediaGalleriesPrefsVersionKey, gallery.prefs_version);
273 bool HasAutoDetectedGalleryPermission(const extensions::Extension& extension) {
274 extensions::MediaGalleriesPermission::CheckParam param(
275 extensions::MediaGalleriesPermission::kAllAutoDetectedPermission);
276 return extensions::PermissionsData::CheckAPIPermissionWithParam(
277 &extension, extensions::APIPermission::kMediaGalleries, ¶m);
280 // Retrieves the MediaGalleryPermission from the given dictionary; DCHECKs on
282 bool GetMediaGalleryPermissionFromDictionary(
283 const base::DictionaryValue* dict,
284 MediaGalleryPermission* out_permission) {
285 std::string string_id;
286 if (dict->GetString(kMediaGalleryIdKey, &string_id) &&
287 base::StringToUint64(string_id, &out_permission->pref_id) &&
288 dict->GetBoolean(kMediaGalleryHasPermissionKey,
289 &out_permission->has_permission)) {
296 base::string16 GetDisplayNameForDevice(uint64 storage_size_in_bytes,
297 const base::string16& name) {
298 DCHECK(!name.empty());
299 return (storage_size_in_bytes == 0) ?
301 ui::FormatBytes(storage_size_in_bytes) + base::ASCIIToUTF16(" ") + name;
304 // For a device with |device_name| and a relative path |sub_folder|, construct
305 // a display name. If |sub_folder| is empty, then just return |device_name|.
306 base::string16 GetDisplayNameForSubFolder(const base::string16& device_name,
307 const base::FilePath& sub_folder) {
308 if (sub_folder.empty())
310 return (sub_folder.BaseName().LossyDisplayName() +
311 base::ASCIIToUTF16(" - ") +
315 base::string16 GetFullProductName(const base::string16& vendor_name,
316 const base::string16& model_name) {
317 if (vendor_name.empty() && model_name.empty())
318 return base::string16();
320 base::string16 product_name;
321 if (vendor_name.empty())
322 product_name = model_name;
323 else if (model_name.empty())
324 product_name = vendor_name;
325 else if (!vendor_name.empty() && !model_name.empty())
326 product_name = vendor_name + base::UTF8ToUTF16(", ") + model_name;
333 MediaGalleryPrefInfo::MediaGalleryPrefInfo()
334 : pref_id(kInvalidMediaGalleryPrefId),
336 total_size_in_bytes(0),
337 volume_metadata_valid(false),
344 MediaGalleryPrefInfo::~MediaGalleryPrefInfo() {}
346 base::FilePath MediaGalleryPrefInfo::AbsolutePath() const {
347 base::FilePath base_path = MediaStorageUtil::FindDevicePathById(device_id);
348 DCHECK(!path.IsAbsolute());
349 return base_path.empty() ? base_path : base_path.Append(path);
352 bool MediaGalleryPrefInfo::IsBlackListedType() const {
353 return type == kBlackListed || type == kRemovedScan;
356 base::string16 MediaGalleryPrefInfo::GetGalleryDisplayName() const {
357 if (!StorageInfo::IsRemovableDevice(device_id)) {
358 // For fixed storage, the default name is the fully qualified directory
359 // name, or in the case of a root directory, the root directory name.
360 // Exception: ChromeOS -- the full pathname isn't visible there, so only
361 // the directory name is used.
362 base::FilePath path = AbsolutePath();
363 if (!display_name.empty())
366 #if defined(OS_CHROMEOS)
367 // See chrome/browser/chromeos/fileapi/file_system_backend.cc
368 base::FilePath download_path;
369 if (PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS_SAFE, &download_path)) {
370 base::FilePath relative;
371 if (download_path.AppendRelativePath(path, &relative))
372 return relative.LossyDisplayName();
374 return path.BaseName().LossyDisplayName();
376 return path.LossyDisplayName();
380 base::string16 name = display_name;
384 name = GetFullProductName(vendor_name, model_name);
386 name = l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNLABELED_DEVICE);
388 name = GetDisplayNameForDevice(total_size_in_bytes, name);
391 name = GetDisplayNameForSubFolder(name, path);
396 base::string16 MediaGalleryPrefInfo::GetGalleryTooltip() const {
397 return AbsolutePath().LossyDisplayName();
400 base::string16 MediaGalleryPrefInfo::GetGalleryAdditionalDetails() const {
401 base::string16 attached;
402 if (StorageInfo::IsRemovableDevice(device_id)) {
403 if (MediaStorageUtil::IsRemovableStorageAttached(device_id)) {
404 attached = l10n_util::GetStringUTF16(
405 IDS_MEDIA_GALLERIES_DIALOG_DEVICE_ATTACHED);
406 } else if (!last_attach_time.is_null()) {
407 attached = l10n_util::GetStringFUTF16(
408 IDS_MEDIA_GALLERIES_LAST_ATTACHED,
409 base::TimeFormatShortDateNumeric(last_attach_time));
411 attached = l10n_util::GetStringUTF16(
412 IDS_MEDIA_GALLERIES_DIALOG_DEVICE_NOT_ATTACHED);
419 bool MediaGalleryPrefInfo::IsGalleryAvailable() const {
420 return !StorageInfo::IsRemovableDevice(device_id) ||
421 MediaStorageUtil::IsRemovableStorageAttached(device_id);
424 MediaGalleriesPreferences::GalleryChangeObserver::~GalleryChangeObserver() {}
426 MediaGalleriesPreferences::MediaGalleriesPreferences(Profile* profile)
427 : initialized_(false),
428 pre_initialization_callbacks_waiting_(0),
430 extension_prefs_for_testing_(NULL),
431 weak_factory_(this) {
434 MediaGalleriesPreferences::~MediaGalleriesPreferences() {
435 if (StorageMonitor::GetInstance())
436 StorageMonitor::GetInstance()->RemoveObserver(this);
439 void MediaGalleriesPreferences::EnsureInitialized(base::Closure callback) {
440 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
442 if (IsInitialized()) {
443 if (!callback.is_null())
448 on_initialize_callbacks_.push_back(callback);
449 if (on_initialize_callbacks_.size() > 1)
452 // This counter must match the number of async methods dispatched below.
453 // It cannot be incremented inline with each callback, as some may return
454 // synchronously, decrement the counter to 0, and prematurely trigger
455 // FinishInitialization.
456 pre_initialization_callbacks_waiting_ = 4;
458 // Check whether we should be initializing -- are there any extensions that
459 // are using media galleries?
460 media_galleries::UsageCount(media_galleries::PREFS_INITIALIZED);
461 if (NumberExtensionsUsingMediaGalleries(profile_) == 0) {
462 media_galleries::UsageCount(media_galleries::PREFS_INITIALIZED_ERROR);
465 // We determine the freshness of the profile here, before any of the finders
466 // return and add media galleries to it.
467 StorageMonitor::GetInstance()->EnsureInitialized(
468 base::Bind(&MediaGalleriesPreferences::OnStorageMonitorInit,
469 weak_factory_.GetWeakPtr(),
470 !APIHasBeenUsed(profile_) /* add_default_galleries */));
472 // Look for optional default galleries every time.
473 iapps::FindITunesLibrary(
474 base::Bind(&MediaGalleriesPreferences::OnFinderDeviceID,
475 weak_factory_.GetWeakPtr()));
477 picasa::FindPicasaDatabase(
478 base::Bind(&MediaGalleriesPreferences::OnFinderDeviceID,
479 weak_factory_.GetWeakPtr()));
481 iapps::FindIPhotoLibrary(
482 base::Bind(&MediaGalleriesPreferences::OnFinderDeviceID,
483 weak_factory_.GetWeakPtr()));
486 bool MediaGalleriesPreferences::IsInitialized() const { return initialized_; }
488 Profile* MediaGalleriesPreferences::profile() { return profile_; }
490 void MediaGalleriesPreferences::OnInitializationCallbackReturned() {
491 DCHECK(!IsInitialized());
492 DCHECK_GT(pre_initialization_callbacks_waiting_, 0);
493 if (--pre_initialization_callbacks_waiting_ == 0)
494 FinishInitialization();
497 void MediaGalleriesPreferences::FinishInitialization() {
498 DCHECK(!IsInitialized());
502 StorageMonitor* monitor = StorageMonitor::GetInstance();
503 DCHECK(monitor->IsInitialized());
507 StorageMonitor::GetInstance()->AddObserver(this);
509 std::vector<StorageInfo> existing_devices =
510 monitor->GetAllAvailableStorages();
511 for (size_t i = 0; i < existing_devices.size(); i++) {
512 if (!(StorageInfo::IsMediaDevice(existing_devices[i].device_id()) &&
513 StorageInfo::IsRemovableDevice(existing_devices[i].device_id())))
515 AddGallery(existing_devices[i].device_id(),
517 MediaGalleryPrefInfo::kAutoDetected,
518 existing_devices[i].storage_label(),
519 existing_devices[i].vendor_name(),
520 existing_devices[i].model_name(),
521 existing_devices[i].total_size_in_bytes(),
522 base::Time::Now(), 0, 0, 0);
525 for (std::vector<base::Closure>::iterator iter =
526 on_initialize_callbacks_.begin();
527 iter != on_initialize_callbacks_.end();
531 on_initialize_callbacks_.clear();
534 void MediaGalleriesPreferences::AddDefaultGalleries() {
535 const int kDirectoryKeys[] = {
536 chrome::DIR_USER_MUSIC,
537 chrome::DIR_USER_PICTURES,
538 chrome::DIR_USER_VIDEOS,
541 for (size_t i = 0; i < arraysize(kDirectoryKeys); ++i) {
543 if (!PathService::Get(kDirectoryKeys[i], &path))
546 base::FilePath relative_path;
548 if (MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) {
549 AddGalleryInternal(info.device_id(), info.name(), relative_path,
550 MediaGalleryPrefInfo::kAutoDetected,
551 info.storage_label(), info.vendor_name(),
552 info.model_name(), info.total_size_in_bytes(),
553 base::Time(), true, 0, 0, 0, kCurrentPrefsVersion);
558 bool MediaGalleriesPreferences::UpdateDeviceIDForSingletonType(
559 const std::string& device_id) {
560 StorageInfo::Type singleton_type;
561 if (!StorageInfo::CrackDeviceId(device_id, &singleton_type, NULL))
564 PrefService* prefs = profile_->GetPrefs();
565 scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
566 prefs, prefs::kMediaGalleriesRememberedGalleries));
567 base::ListValue* list = update->Get();
568 for (base::ListValue::iterator iter = list->begin();
569 iter != list->end(); ++iter) {
570 // All of these calls should succeed, but preferences file can be corrupt.
571 base::DictionaryValue* dict;
572 if (!(*iter)->GetAsDictionary(&dict))
574 std::string this_device_id;
575 if (!dict->GetString(kMediaGalleriesDeviceIdKey, &this_device_id))
577 if (this_device_id == device_id)
578 return true; // No update is necessary.
579 StorageInfo::Type device_type;
580 if (!StorageInfo::CrackDeviceId(this_device_id, &device_type, NULL))
583 if (device_type == singleton_type) {
584 dict->SetString(kMediaGalleriesDeviceIdKey, device_id);
585 update.reset(); // commits the update.
587 MediaGalleryPrefId pref_id;
588 if (GetPrefId(*dict, &pref_id)) {
589 FOR_EACH_OBSERVER(GalleryChangeObserver,
590 gallery_change_observers_,
591 OnGalleryInfoUpdated(this, pref_id));
599 void MediaGalleriesPreferences::OnStorageMonitorInit(
600 bool add_default_galleries) {
601 if (add_default_galleries)
602 AddDefaultGalleries();
603 OnInitializationCallbackReturned();
606 void MediaGalleriesPreferences::OnFinderDeviceID(const std::string& device_id) {
607 if (!device_id.empty() && !UpdateDeviceIDForSingletonType(device_id)) {
608 std::string gallery_name;
609 if (StorageInfo::IsIPhotoDevice(device_id))
610 gallery_name = kIPhotoGalleryName;
611 else if (StorageInfo::IsITunesDevice(device_id))
612 gallery_name = kITunesGalleryName;
613 else if (StorageInfo::IsPicasaDevice(device_id))
614 gallery_name = kPicasaGalleryName;
618 AddGalleryInternal(device_id, base::ASCIIToUTF16(gallery_name),
619 base::FilePath(), MediaGalleryPrefInfo::kAutoDetected,
620 base::string16(), base::string16(), base::string16(), 0,
621 base::Time(), false, 0, 0, 0, kCurrentPrefsVersion);
624 OnInitializationCallbackReturned();
627 void MediaGalleriesPreferences::InitFromPrefs() {
628 known_galleries_.clear();
631 PrefService* prefs = profile_->GetPrefs();
632 const base::ListValue* list = prefs->GetList(
633 prefs::kMediaGalleriesRememberedGalleries);
635 for (base::ListValue::const_iterator it = list->begin();
636 it != list->end(); ++it) {
637 const base::DictionaryValue* dict = NULL;
638 if (!(*it)->GetAsDictionary(&dict))
641 MediaGalleryPrefInfo gallery_info;
642 if (!PopulateGalleryPrefInfoFromDictionary(*dict, &gallery_info))
645 known_galleries_[gallery_info.pref_id] = gallery_info;
646 device_map_[gallery_info.device_id].insert(gallery_info.pref_id);
651 void MediaGalleriesPreferences::AddGalleryChangeObserver(
652 GalleryChangeObserver* observer) {
653 DCHECK(IsInitialized());
654 gallery_change_observers_.AddObserver(observer);
657 void MediaGalleriesPreferences::RemoveGalleryChangeObserver(
658 GalleryChangeObserver* observer) {
659 DCHECK(IsInitialized());
660 gallery_change_observers_.RemoveObserver(observer);
663 void MediaGalleriesPreferences::OnRemovableStorageAttached(
664 const StorageInfo& info) {
665 DCHECK(IsInitialized());
666 if (!StorageInfo::IsMediaDevice(info.device_id()))
669 AddGallery(info.device_id(), base::FilePath(),
670 MediaGalleryPrefInfo::kAutoDetected, info.storage_label(),
671 info.vendor_name(), info.model_name(), info.total_size_in_bytes(),
672 base::Time::Now(), 0, 0, 0);
675 bool MediaGalleriesPreferences::LookUpGalleryByPath(
676 const base::FilePath& path,
677 MediaGalleryPrefInfo* gallery_info) const {
678 DCHECK(IsInitialized());
680 base::FilePath relative_path;
681 if (!MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) {
683 *gallery_info = MediaGalleryPrefInfo();
687 relative_path = relative_path.NormalizePathSeparators();
688 MediaGalleryPrefIdSet galleries_on_device =
689 LookUpGalleriesByDeviceId(info.device_id());
690 for (MediaGalleryPrefIdSet::const_iterator it = galleries_on_device.begin();
691 it != galleries_on_device.end();
693 const MediaGalleryPrefInfo& gallery = known_galleries_.find(*it)->second;
694 if (gallery.path != relative_path)
698 *gallery_info = gallery;
702 // This method is called by controller::FilesSelected when the user
703 // adds a new gallery. Control reaches here when the selected gallery is
704 // on a volume we know about, but have no gallery already for. Returns
705 // hypothetical data to the caller about what the prefs will look like
706 // if the gallery is added.
707 // TODO(gbillock): split this out into another function so it doesn't
710 gallery_info->pref_id = kInvalidMediaGalleryPrefId;
711 gallery_info->device_id = info.device_id();
712 gallery_info->path = relative_path;
713 gallery_info->type = MediaGalleryPrefInfo::kInvalidType;
714 gallery_info->volume_label = info.storage_label();
715 gallery_info->vendor_name = info.vendor_name();
716 gallery_info->model_name = info.model_name();
717 gallery_info->total_size_in_bytes = info.total_size_in_bytes();
718 gallery_info->last_attach_time = base::Time::Now();
719 gallery_info->volume_metadata_valid = true;
720 gallery_info->prefs_version = kCurrentPrefsVersion;
725 MediaGalleryPrefIdSet MediaGalleriesPreferences::LookUpGalleriesByDeviceId(
726 const std::string& device_id) const {
727 DeviceIdPrefIdsMap::const_iterator found = device_map_.find(device_id);
728 if (found == device_map_.end())
729 return MediaGalleryPrefIdSet();
730 return found->second;
733 base::FilePath MediaGalleriesPreferences::LookUpGalleryPathForExtension(
734 MediaGalleryPrefId gallery_id,
735 const extensions::Extension* extension,
736 bool include_unpermitted_galleries) {
737 DCHECK(IsInitialized());
739 if (!include_unpermitted_galleries &&
740 !ContainsKey(GalleriesForExtension(*extension), gallery_id))
741 return base::FilePath();
743 MediaGalleriesPrefInfoMap::const_iterator it =
744 known_galleries_.find(gallery_id);
745 if (it == known_galleries_.end())
746 return base::FilePath();
747 return MediaStorageUtil::FindDevicePathById(it->second.device_id);
750 MediaGalleryPrefId MediaGalleriesPreferences::AddGallery(
751 const std::string& device_id,
752 const base::FilePath& relative_path,
753 MediaGalleryPrefInfo::Type type,
754 const base::string16& volume_label,
755 const base::string16& vendor_name,
756 const base::string16& model_name,
757 uint64 total_size_in_bytes,
758 base::Time last_attach_time,
762 DCHECK(IsInitialized());
763 return AddGalleryInternal(device_id, base::string16(), relative_path,
764 type, volume_label, vendor_name, model_name,
765 total_size_in_bytes, last_attach_time, true,
766 audio_count, image_count, video_count,
767 kCurrentPrefsVersion);
770 MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryInternal(
771 const std::string& device_id, const base::string16& display_name,
772 const base::FilePath& relative_path, MediaGalleryPrefInfo::Type type,
773 const base::string16& volume_label, const base::string16& vendor_name,
774 const base::string16& model_name, uint64 total_size_in_bytes,
775 base::Time last_attach_time, bool volume_metadata_valid,
776 int audio_count, int image_count, int video_count, int prefs_version) {
777 DCHECK(type == MediaGalleryPrefInfo::kUserAdded ||
778 type == MediaGalleryPrefInfo::kAutoDetected ||
779 type == MediaGalleryPrefInfo::kScanResult);
780 base::FilePath normalized_relative_path =
781 relative_path.NormalizePathSeparators();
782 MediaGalleryPrefIdSet galleries_on_device =
783 LookUpGalleriesByDeviceId(device_id);
784 for (MediaGalleryPrefIdSet::const_iterator pref_id_it =
785 galleries_on_device.begin();
786 pref_id_it != galleries_on_device.end();
788 const MediaGalleryPrefInfo& existing =
789 known_galleries_.find(*pref_id_it)->second;
790 if (existing.path != normalized_relative_path)
793 bool update_gallery_type = false;
794 MediaGalleryPrefInfo::Type new_type = existing.type;
795 if (type == MediaGalleryPrefInfo::kUserAdded) {
796 if (existing.type == MediaGalleryPrefInfo::kBlackListed) {
797 new_type = MediaGalleryPrefInfo::kAutoDetected;
798 update_gallery_type = true;
800 if (existing.type == MediaGalleryPrefInfo::kRemovedScan) {
801 new_type = MediaGalleryPrefInfo::kUserAdded;
802 update_gallery_type = true;
806 // Status quo: In M27 and M28, galleries added manually use version 0,
807 // and galleries added automatically (including default galleries) use
808 // version 1. The name override is used by default galleries as well
809 // as all device attach events.
810 // We want to upgrade the name if the existing version is < 2. Leave it
811 // alone if the existing display name is set with version == 2 and the
812 // proposed new name is empty.
813 bool update_gallery_name = existing.display_name != display_name;
814 if (existing.prefs_version == 2 && !existing.display_name.empty() &&
815 display_name.empty()) {
816 update_gallery_name = false;
818 bool update_gallery_metadata = volume_metadata_valid &&
819 ((existing.volume_label != volume_label) ||
820 (existing.vendor_name != vendor_name) ||
821 (existing.model_name != model_name) ||
822 (existing.total_size_in_bytes != total_size_in_bytes) ||
823 (existing.last_attach_time != last_attach_time));
825 bool update_scan_counts =
826 new_type != MediaGalleryPrefInfo::kRemovedScan &&
827 new_type != MediaGalleryPrefInfo::kBlackListed &&
828 (audio_count > 0 || image_count > 0 || video_count > 0 ||
829 existing.audio_count || existing.image_count || existing.video_count);
831 if (!update_gallery_name && !update_gallery_type &&
832 !update_gallery_metadata && !update_scan_counts)
835 PrefService* prefs = profile_->GetPrefs();
836 scoped_ptr<ListPrefUpdate> update(
837 new ListPrefUpdate(prefs, prefs::kMediaGalleriesRememberedGalleries));
838 base::ListValue* list = update->Get();
840 for (base::ListValue::const_iterator list_iter = list->begin();
841 list_iter != list->end();
843 base::DictionaryValue* dict;
844 MediaGalleryPrefId iter_id;
845 if ((*list_iter)->GetAsDictionary(&dict) &&
846 GetPrefId(*dict, &iter_id) &&
847 *pref_id_it == iter_id) {
848 if (update_gallery_type)
849 dict->SetString(kMediaGalleriesTypeKey, TypeToStringValue(new_type));
850 if (update_gallery_name)
851 dict->SetString(kMediaGalleriesDisplayNameKey, display_name);
852 if (update_gallery_metadata) {
853 dict->SetString(kMediaGalleriesVolumeLabelKey, volume_label);
854 dict->SetString(kMediaGalleriesVendorNameKey, vendor_name);
855 dict->SetString(kMediaGalleriesModelNameKey, model_name);
856 dict->SetDouble(kMediaGalleriesSizeKey, total_size_in_bytes);
857 dict->SetDouble(kMediaGalleriesLastAttachTimeKey,
858 last_attach_time.ToInternalValue());
860 if (update_scan_counts) {
861 dict->SetInteger(kMediaGalleriesScanAudioCountKey, audio_count);
862 dict->SetInteger(kMediaGalleriesScanImageCountKey, image_count);
863 dict->SetInteger(kMediaGalleriesScanVideoCountKey, video_count);
865 dict->SetInteger(kMediaGalleriesPrefsVersionKey, prefs_version);
870 // Commits the prefs update.
874 FOR_EACH_OBSERVER(GalleryChangeObserver, gallery_change_observers_,
875 OnGalleryInfoUpdated(this, *pref_id_it));
879 PrefService* prefs = profile_->GetPrefs();
881 MediaGalleryPrefInfo gallery_info;
882 gallery_info.pref_id = prefs->GetUint64(prefs::kMediaGalleriesUniqueId);
883 prefs->SetUint64(prefs::kMediaGalleriesUniqueId, gallery_info.pref_id + 1);
884 gallery_info.display_name = display_name;
885 gallery_info.device_id = device_id;
886 gallery_info.path = normalized_relative_path;
887 gallery_info.type = type;
888 gallery_info.volume_label = volume_label;
889 gallery_info.vendor_name = vendor_name;
890 gallery_info.model_name = model_name;
891 gallery_info.total_size_in_bytes = total_size_in_bytes;
892 gallery_info.last_attach_time = last_attach_time;
893 gallery_info.volume_metadata_valid = volume_metadata_valid;
894 gallery_info.audio_count = audio_count;
895 gallery_info.image_count = image_count;
896 gallery_info.video_count = video_count;
897 gallery_info.prefs_version = prefs_version;
900 ListPrefUpdate update(prefs, prefs::kMediaGalleriesRememberedGalleries);
901 base::ListValue* list = update.Get();
902 list->Append(CreateGalleryPrefInfoDictionary(gallery_info));
905 FOR_EACH_OBSERVER(GalleryChangeObserver,
906 gallery_change_observers_,
907 OnGalleryAdded(this, gallery_info.pref_id));
909 return gallery_info.pref_id;
912 MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryByPath(
913 const base::FilePath& path, MediaGalleryPrefInfo::Type type) {
914 DCHECK(IsInitialized());
915 MediaGalleryPrefInfo gallery_info;
916 if (LookUpGalleryByPath(path, &gallery_info) &&
917 !gallery_info.IsBlackListedType()) {
918 return gallery_info.pref_id;
920 return AddGalleryInternal(gallery_info.device_id,
921 gallery_info.display_name,
924 gallery_info.volume_label,
925 gallery_info.vendor_name,
926 gallery_info.model_name,
927 gallery_info.total_size_in_bytes,
928 gallery_info.last_attach_time,
929 gallery_info.volume_metadata_valid,
931 kCurrentPrefsVersion);
934 void MediaGalleriesPreferences::ForgetGalleryById(MediaGalleryPrefId id) {
935 EraseOrBlacklistGalleryById(id, false);
938 void MediaGalleriesPreferences::EraseGalleryById(MediaGalleryPrefId id) {
939 EraseOrBlacklistGalleryById(id, true);
942 void MediaGalleriesPreferences::EraseOrBlacklistGalleryById(
943 MediaGalleryPrefId id, bool erase) {
944 DCHECK(IsInitialized());
945 PrefService* prefs = profile_->GetPrefs();
946 scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate(
947 prefs, prefs::kMediaGalleriesRememberedGalleries));
948 base::ListValue* list = update->Get();
950 if (!ContainsKey(known_galleries_, id))
953 for (base::ListValue::iterator iter = list->begin();
954 iter != list->end(); ++iter) {
955 base::DictionaryValue* dict;
956 MediaGalleryPrefId iter_id;
957 if ((*iter)->GetAsDictionary(&dict) && GetPrefId(*dict, &iter_id) &&
959 RemoveGalleryPermissionsFromPrefs(id);
960 MediaGalleryPrefInfo::Type type;
961 if (!erase && GetType(*dict, &type) &&
962 (type == MediaGalleryPrefInfo::kAutoDetected ||
963 type == MediaGalleryPrefInfo::kScanResult)) {
964 if (type == MediaGalleryPrefInfo::kAutoDetected) {
965 dict->SetString(kMediaGalleriesTypeKey,
966 kMediaGalleriesTypeBlackListedValue);
968 dict->SetString(kMediaGalleriesTypeKey,
969 kMediaGalleriesTypeRemovedScanValue);
970 dict->SetInteger(kMediaGalleriesScanAudioCountKey, 0);
971 dict->SetInteger(kMediaGalleriesScanImageCountKey, 0);
972 dict->SetInteger(kMediaGalleriesScanVideoCountKey, 0);
975 list->Erase(iter, NULL);
977 update.reset(NULL); // commits the update.
980 FOR_EACH_OBSERVER(GalleryChangeObserver,
981 gallery_change_observers_,
982 OnGalleryRemoved(this, id));
988 bool MediaGalleriesPreferences::NonAutoGalleryHasPermission(
989 MediaGalleryPrefId id) const {
990 DCHECK(IsInitialized());
991 DCHECK(!ContainsKey(known_galleries_, id) ||
992 known_galleries_.find(id)->second.type !=
993 MediaGalleryPrefInfo::kAutoDetected);
994 ExtensionPrefs* prefs = GetExtensionPrefs();
995 const base::DictionaryValue* extensions =
996 prefs->pref_service()->GetDictionary(extensions::pref_names::kExtensions);
1000 for (base::DictionaryValue::Iterator iter(*extensions); !iter.IsAtEnd();
1002 if (!extensions::Extension::IdIsValid(iter.key())) {
1006 std::vector<MediaGalleryPermission> permissions =
1007 GetGalleryPermissionsFromPrefs(iter.key());
1008 for (std::vector<MediaGalleryPermission>::const_iterator it =
1009 permissions.begin(); it != permissions.end(); ++it) {
1010 if (it->pref_id == id) {
1011 if (it->has_permission)
1020 MediaGalleryPrefIdSet MediaGalleriesPreferences::GalleriesForExtension(
1021 const extensions::Extension& extension) const {
1022 DCHECK(IsInitialized());
1023 MediaGalleryPrefIdSet result;
1025 if (HasAutoDetectedGalleryPermission(extension)) {
1026 for (MediaGalleriesPrefInfoMap::const_iterator it =
1027 known_galleries_.begin(); it != known_galleries_.end(); ++it) {
1028 if (it->second.type == MediaGalleryPrefInfo::kAutoDetected)
1029 result.insert(it->second.pref_id);
1033 std::vector<MediaGalleryPermission> stored_permissions =
1034 GetGalleryPermissionsFromPrefs(extension.id());
1035 for (std::vector<MediaGalleryPermission>::const_iterator it =
1036 stored_permissions.begin(); it != stored_permissions.end(); ++it) {
1037 if (!it->has_permission) {
1038 result.erase(it->pref_id);
1040 MediaGalleriesPrefInfoMap::const_iterator gallery =
1041 known_galleries_.find(it->pref_id);
1042 DCHECK(gallery != known_galleries_.end());
1043 if (!gallery->second.IsBlackListedType()) {
1044 result.insert(it->pref_id);
1046 NOTREACHED() << gallery->second.device_id;
1053 bool MediaGalleriesPreferences::SetGalleryPermissionForExtension(
1054 const extensions::Extension& extension,
1055 MediaGalleryPrefId pref_id,
1056 bool has_permission) {
1057 DCHECK(IsInitialized());
1058 // The gallery may not exist anymore if the user opened a second config
1059 // surface concurrently and removed it. Drop the permission update if so.
1060 MediaGalleriesPrefInfoMap::const_iterator gallery_info =
1061 known_galleries_.find(pref_id);
1062 if (gallery_info == known_galleries_.end())
1065 bool default_permission = false;
1066 if (gallery_info->second.type == MediaGalleryPrefInfo::kAutoDetected)
1067 default_permission = HasAutoDetectedGalleryPermission(extension);
1068 // When the permission matches the default, we don't need to remember it.
1069 if (has_permission == default_permission) {
1070 if (!UnsetGalleryPermissionInPrefs(extension.id(), pref_id))
1071 // If permission wasn't set, assume nothing has changed.
1074 if (!SetGalleryPermissionInPrefs(extension.id(), pref_id, has_permission))
1078 FOR_EACH_OBSERVER(GalleryChangeObserver,
1079 gallery_change_observers_,
1080 OnPermissionAdded(this, extension.id(), pref_id));
1082 FOR_EACH_OBSERVER(GalleryChangeObserver,
1083 gallery_change_observers_,
1084 OnPermissionRemoved(this, extension.id(), pref_id));
1088 const MediaGalleriesPrefInfoMap& MediaGalleriesPreferences::known_galleries()
1090 DCHECK(IsInitialized());
1091 return known_galleries_;
1094 base::Time MediaGalleriesPreferences::GetLastScanCompletionTime() const {
1095 int64 last_scan_time_internal =
1096 profile_->GetPrefs()->GetInt64(prefs::kMediaGalleriesLastScanTime);
1097 return base::Time::FromInternalValue(last_scan_time_internal);
1100 void MediaGalleriesPreferences::SetLastScanCompletionTime(
1101 const base::Time& time) {
1102 profile_->GetPrefs()->SetInt64(prefs::kMediaGalleriesLastScanTime,
1103 time.ToInternalValue());
1106 void MediaGalleriesPreferences::Shutdown() {
1107 weak_factory_.InvalidateWeakPtrs();
1112 bool MediaGalleriesPreferences::APIHasBeenUsed(Profile* profile) {
1113 MediaGalleryPrefId current_id =
1114 profile->GetPrefs()->GetUint64(prefs::kMediaGalleriesUniqueId);
1115 return current_id != kInvalidMediaGalleryPrefId + 1;
1119 void MediaGalleriesPreferences::RegisterProfilePrefs(
1120 user_prefs::PrefRegistrySyncable* registry) {
1121 registry->RegisterListPref(prefs::kMediaGalleriesRememberedGalleries,
1122 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
1123 registry->RegisterUint64Pref(
1124 prefs::kMediaGalleriesUniqueId,
1125 kInvalidMediaGalleryPrefId + 1,
1126 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
1127 registry->RegisterInt64Pref(
1128 prefs::kMediaGalleriesLastScanTime,
1129 base::Time().ToInternalValue(),
1130 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
1133 bool MediaGalleriesPreferences::SetGalleryPermissionInPrefs(
1134 const std::string& extension_id,
1135 MediaGalleryPrefId gallery_id,
1137 DCHECK(IsInitialized());
1138 ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(),
1140 kMediaGalleriesPermissions);
1141 base::ListValue* permissions = update.Get();
1143 permissions = update.Create();
1145 // If the gallery is already in the list, update the permission...
1146 for (base::ListValue::iterator iter = permissions->begin();
1147 iter != permissions->end(); ++iter) {
1148 base::DictionaryValue* dict = NULL;
1149 if (!(*iter)->GetAsDictionary(&dict))
1151 MediaGalleryPermission perm;
1152 if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
1154 if (perm.pref_id == gallery_id) {
1155 if (has_access != perm.has_permission) {
1156 dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access);
1164 // ...Otherwise, add a new entry for the gallery.
1165 base::DictionaryValue* dict = new base::DictionaryValue;
1166 dict->SetString(kMediaGalleryIdKey, base::Uint64ToString(gallery_id));
1167 dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access);
1168 permissions->Append(dict);
1172 bool MediaGalleriesPreferences::UnsetGalleryPermissionInPrefs(
1173 const std::string& extension_id,
1174 MediaGalleryPrefId gallery_id) {
1175 DCHECK(IsInitialized());
1176 ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(),
1178 kMediaGalleriesPermissions);
1179 base::ListValue* permissions = update.Get();
1183 for (base::ListValue::iterator iter = permissions->begin();
1184 iter != permissions->end(); ++iter) {
1185 const base::DictionaryValue* dict = NULL;
1186 if (!(*iter)->GetAsDictionary(&dict))
1188 MediaGalleryPermission perm;
1189 if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
1191 if (perm.pref_id == gallery_id) {
1192 permissions->Erase(iter, NULL);
1199 std::vector<MediaGalleryPermission>
1200 MediaGalleriesPreferences::GetGalleryPermissionsFromPrefs(
1201 const std::string& extension_id) const {
1202 DCHECK(IsInitialized());
1203 std::vector<MediaGalleryPermission> result;
1204 const base::ListValue* permissions;
1205 if (!GetExtensionPrefs()->ReadPrefAsList(extension_id,
1206 kMediaGalleriesPermissions,
1211 for (base::ListValue::const_iterator iter = permissions->begin();
1212 iter != permissions->end(); ++iter) {
1213 base::DictionaryValue* dict = NULL;
1214 if (!(*iter)->GetAsDictionary(&dict))
1216 MediaGalleryPermission perm;
1217 if (!GetMediaGalleryPermissionFromDictionary(dict, &perm))
1219 result.push_back(perm);
1225 void MediaGalleriesPreferences::RemoveGalleryPermissionsFromPrefs(
1226 MediaGalleryPrefId gallery_id) {
1227 DCHECK(IsInitialized());
1228 ExtensionPrefs* prefs = GetExtensionPrefs();
1229 const base::DictionaryValue* extensions =
1230 prefs->pref_service()->GetDictionary(extensions::pref_names::kExtensions);
1234 for (base::DictionaryValue::Iterator iter(*extensions); !iter.IsAtEnd();
1236 if (!extensions::Extension::IdIsValid(iter.key())) {
1240 UnsetGalleryPermissionInPrefs(iter.key(), gallery_id);
1244 ExtensionPrefs* MediaGalleriesPreferences::GetExtensionPrefs() const {
1245 DCHECK(IsInitialized());
1246 if (extension_prefs_for_testing_)
1247 return extension_prefs_for_testing_;
1248 return extensions::ExtensionPrefs::Get(profile_);
1251 void MediaGalleriesPreferences::SetExtensionPrefsForTesting(
1252 extensions::ExtensionPrefs* extension_prefs) {
1253 DCHECK(IsInitialized());
1254 extension_prefs_for_testing_ = extension_prefs;