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. 4 5 #include "chrome/browser/media_galleries/media_galleries_preferences.h" 6 7 #include "base/i18n/time_formatting.h" 8 #include "base/path_service.h" 9 #include "base/prefs/pref_service.h" 10 #include "base/stl_util.h" 11 #include "base/strings/string16.h" 12 #include "base/strings/string_number_conversions.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/extensions/api/media_galleries_private/media_galleries_private_api.h" 17 #include "chrome/browser/extensions/extension_prefs.h" 18 #include "chrome/browser/extensions/extension_service.h" 19 #include "chrome/browser/extensions/extension_system.h" 20 #include "chrome/browser/media_galleries/fileapi/itunes_finder.h" 21 #include "chrome/browser/media_galleries/fileapi/picasa/picasa_finder.h" 22 #include "chrome/browser/media_galleries/media_file_system_registry.h" 23 #include "chrome/browser/prefs/scoped_user_pref_update.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/storage_monitor/media_storage_util.h" 26 #include "chrome/browser/storage_monitor/storage_monitor.h" 27 #include "chrome/common/chrome_paths.h" 28 #include "chrome/common/extensions/extension.h" 29 #include "chrome/common/extensions/permissions/api_permission.h" 30 #include "chrome/common/extensions/permissions/media_galleries_permission.h" 31 #include "chrome/common/extensions/permissions/permissions_data.h" 32 #include "chrome/common/pref_names.h" 33 #include "components/user_prefs/pref_registry_syncable.h" 34 #include "grit/generated_resources.h" 35 #include "ui/base/l10n/l10n_util.h" 36 #include "ui/base/text/bytes_formatting.h" 37 38 using base::DictionaryValue; 39 using base::ListValue; 40 using extensions::ExtensionPrefs; 41 42 namespace chrome { 43 44 namespace { 45 46 // Pref key for the list of media gallery permissions. 47 const char kMediaGalleriesPermissions[] = "media_galleries_permissions"; 48 // Pref key for Media Gallery ID. 49 const char kMediaGalleryIdKey[] = "id"; 50 // Pref key for Media Gallery Permission Value. 51 const char kMediaGalleryHasPermissionKey[] = "has_permission"; 52 53 const char kMediaGalleriesDeviceIdKey[] = "deviceId"; 54 const char kMediaGalleriesDisplayNameKey[] = "displayName"; 55 const char kMediaGalleriesPathKey[] = "path"; 56 const char kMediaGalleriesPrefIdKey[] = "prefId"; 57 const char kMediaGalleriesTypeKey[] = "type"; 58 const char kMediaGalleriesVolumeLabelKey[] = "volumeLabel"; 59 const char kMediaGalleriesVendorNameKey[] = "vendorName"; 60 const char kMediaGalleriesModelNameKey[] = "modelName"; 61 const char kMediaGalleriesSizeKey[] = "totalSize"; 62 const char kMediaGalleriesLastAttachTimeKey[] = "lastAttachTime"; 63 const char kMediaGalleriesPrefsVersionKey[] = "preferencesVersion"; 64 65 const char kMediaGalleriesTypeAutoDetectedValue[] = "autoDetected"; 66 const char kMediaGalleriesTypeUserAddedValue[] = "userAdded"; 67 const char kMediaGalleriesTypeBlackListedValue[] = "blackListed"; 68 69 const char kITunesGalleryName[] = "iTunes"; 70 const char kPicasaGalleryName[] = "Picasa"; 71 72 bool GetPrefId(const DictionaryValue& dict, MediaGalleryPrefId* value) { 73 std::string string_id; 74 if (!dict.GetString(kMediaGalleriesPrefIdKey, &string_id) || 75 !base::StringToUint64(string_id, value)) { 76 return false; 77 } 78 79 return true; 80 } 81 82 bool GetType(const DictionaryValue& dict, MediaGalleryPrefInfo::Type* type) { 83 std::string string_type; 84 if (!dict.GetString(kMediaGalleriesTypeKey, &string_type)) 85 return false; 86 87 if (string_type == kMediaGalleriesTypeAutoDetectedValue) { 88 *type = MediaGalleryPrefInfo::kAutoDetected; 89 return true; 90 } 91 if (string_type == kMediaGalleriesTypeUserAddedValue) { 92 *type = MediaGalleryPrefInfo::kUserAdded; 93 return true; 94 } 95 if (string_type == kMediaGalleriesTypeBlackListedValue) { 96 *type = MediaGalleryPrefInfo::kBlackListed; 97 return true; 98 } 99 100 return false; 101 } 102 103 bool PopulateGalleryPrefInfoFromDictionary( 104 const DictionaryValue& dict, MediaGalleryPrefInfo* out_gallery_info) { 105 MediaGalleryPrefId pref_id; 106 string16 display_name; 107 std::string device_id; 108 base::FilePath::StringType path; 109 MediaGalleryPrefInfo::Type type = MediaGalleryPrefInfo::kAutoDetected; 110 string16 volume_label; 111 string16 vendor_name; 112 string16 model_name; 113 double total_size_in_bytes = 0.0; 114 double last_attach_time = 0.0; 115 bool volume_metadata_valid = false; 116 int prefs_version = 0; 117 118 if (!GetPrefId(dict, &pref_id) || 119 !dict.GetString(kMediaGalleriesDeviceIdKey, &device_id) || 120 !dict.GetString(kMediaGalleriesPathKey, &path) || 121 !GetType(dict, &type)) { 122 return false; 123 } 124 125 dict.GetString(kMediaGalleriesDisplayNameKey, &display_name); 126 dict.GetInteger(kMediaGalleriesPrefsVersionKey, &prefs_version); 127 128 if (dict.GetString(kMediaGalleriesVolumeLabelKey, &volume_label) && 129 dict.GetString(kMediaGalleriesVendorNameKey, &vendor_name) && 130 dict.GetString(kMediaGalleriesModelNameKey, &model_name) && 131 dict.GetDouble(kMediaGalleriesSizeKey, &total_size_in_bytes) && 132 dict.GetDouble(kMediaGalleriesLastAttachTimeKey, &last_attach_time)) { 133 volume_metadata_valid = true; 134 } 135 136 out_gallery_info->pref_id = pref_id; 137 out_gallery_info->display_name = display_name; 138 out_gallery_info->device_id = device_id; 139 out_gallery_info->path = base::FilePath(path); 140 out_gallery_info->type = type; 141 out_gallery_info->volume_label = volume_label; 142 out_gallery_info->vendor_name = vendor_name; 143 out_gallery_info->model_name = model_name; 144 out_gallery_info->total_size_in_bytes = total_size_in_bytes; 145 out_gallery_info->last_attach_time = 146 base::Time::FromInternalValue(last_attach_time); 147 out_gallery_info->volume_metadata_valid = volume_metadata_valid; 148 out_gallery_info->prefs_version = prefs_version; 149 150 return true; 151 } 152 153 DictionaryValue* CreateGalleryPrefInfoDictionary( 154 const MediaGalleryPrefInfo& gallery) { 155 DictionaryValue* dict = new DictionaryValue(); 156 dict->SetString(kMediaGalleriesPrefIdKey, 157 base::Uint64ToString(gallery.pref_id)); 158 if (!gallery.volume_metadata_valid) 159 dict->SetString(kMediaGalleriesDisplayNameKey, gallery.display_name); 160 dict->SetString(kMediaGalleriesDeviceIdKey, gallery.device_id); 161 dict->SetString(kMediaGalleriesPathKey, gallery.path.value()); 162 163 const char* type = NULL; 164 switch (gallery.type) { 165 case MediaGalleryPrefInfo::kAutoDetected: 166 type = kMediaGalleriesTypeAutoDetectedValue; 167 break; 168 case MediaGalleryPrefInfo::kUserAdded: 169 type = kMediaGalleriesTypeUserAddedValue; 170 break; 171 case MediaGalleryPrefInfo::kBlackListed: 172 type = kMediaGalleriesTypeBlackListedValue; 173 break; 174 default: 175 NOTREACHED(); 176 break; 177 } 178 dict->SetString(kMediaGalleriesTypeKey, type); 179 180 if (gallery.volume_metadata_valid) { 181 dict->SetString(kMediaGalleriesVolumeLabelKey, gallery.volume_label); 182 dict->SetString(kMediaGalleriesVendorNameKey, gallery.vendor_name); 183 dict->SetString(kMediaGalleriesModelNameKey, gallery.model_name); 184 dict->SetDouble(kMediaGalleriesSizeKey, gallery.total_size_in_bytes); 185 dict->SetDouble(kMediaGalleriesLastAttachTimeKey, 186 gallery.last_attach_time.ToInternalValue()); 187 } 188 189 // Version 0 of the prefs format was that the display_name was always 190 // used to show the user-visible name of the gallery. Version 1 means 191 // that there is an optional display_name, and when it is present, it 192 // overrides the name that would be built from the volume metadata, path, 193 // or whatever other data. So if we see a display_name with version 0, it 194 // means it may be overwritten simply by getting new volume metadata. 195 // A display_name with version 1 should not be overwritten. 196 dict->SetInteger(kMediaGalleriesPrefsVersionKey, gallery.prefs_version); 197 198 return dict; 199 } 200 201 bool HasAutoDetectedGalleryPermission(const extensions::Extension& extension) { 202 extensions::MediaGalleriesPermission::CheckParam param( 203 extensions::MediaGalleriesPermission::kAllAutoDetectedPermission); 204 return extensions::PermissionsData::CheckAPIPermissionWithParam( 205 &extension, extensions::APIPermission::kMediaGalleries, ¶m); 206 } 207 208 // Retrieves the MediaGalleryPermission from the given dictionary; DCHECKs on 209 // failure. 210 bool GetMediaGalleryPermissionFromDictionary( 211 const DictionaryValue* dict, 212 MediaGalleryPermission* out_permission) { 213 std::string string_id; 214 if (dict->GetString(kMediaGalleryIdKey, &string_id) && 215 base::StringToUint64(string_id, &out_permission->pref_id) && 216 dict->GetBoolean(kMediaGalleryHasPermissionKey, 217 &out_permission->has_permission)) { 218 return true; 219 } 220 NOTREACHED(); 221 return false; 222 } 223 224 string16 GetDisplayNameForDevice(uint64 storage_size_in_bytes, 225 const string16& name) { 226 DCHECK(!name.empty()); 227 return (storage_size_in_bytes == 0) ? 228 name : ui::FormatBytes(storage_size_in_bytes) + ASCIIToUTF16(" ") + name; 229 } 230 231 // For a device with |device_name| and a relative path |sub_folder|, construct 232 // a display name. If |sub_folder| is empty, then just return |device_name|. 233 string16 GetDisplayNameForSubFolder(const string16& device_name, 234 const base::FilePath& sub_folder) { 235 if (sub_folder.empty()) 236 return device_name; 237 return (sub_folder.BaseName().LossyDisplayName() + 238 ASCIIToUTF16(" - ") + 239 device_name); 240 } 241 242 string16 GetFullProductName(const string16& vendor_name, 243 const string16& model_name) { 244 if (vendor_name.empty() && model_name.empty()) 245 return string16(); 246 247 string16 product_name; 248 if (vendor_name.empty()) 249 product_name = model_name; 250 else if (model_name.empty()) 251 product_name = vendor_name; 252 else if (!vendor_name.empty() && !model_name.empty()) 253 product_name = vendor_name + UTF8ToUTF16(", ") + model_name; 254 255 return product_name; 256 } 257 258 } // namespace 259 260 MediaGalleryPrefInfo::MediaGalleryPrefInfo() 261 : pref_id(kInvalidMediaGalleryPrefId), 262 type(kInvalidType), 263 total_size_in_bytes(0), 264 volume_metadata_valid(false), 265 prefs_version(0) { 266 } 267 268 MediaGalleryPrefInfo::~MediaGalleryPrefInfo() {} 269 270 base::FilePath MediaGalleryPrefInfo::AbsolutePath() const { 271 base::FilePath base_path = MediaStorageUtil::FindDevicePathById(device_id); 272 DCHECK(!path.IsAbsolute()); 273 return base_path.empty() ? base_path : base_path.Append(path); 274 } 275 276 string16 MediaGalleryPrefInfo::GetGalleryDisplayName() const { 277 if (!StorageInfo::IsRemovableDevice(device_id)) { 278 // For fixed storage, the name is the directory name, or, in the case 279 // of a root directory, the root directory name. 280 // TODO(gbillock): Using only the BaseName can lead to ambiguity. The 281 // tooltip resolves it. Is that enough? 282 base::FilePath path = AbsolutePath(); 283 if (!display_name.empty()) 284 return display_name; 285 if (path == path.DirName()) 286 return path.LossyDisplayName(); 287 return path.BaseName().LossyDisplayName(); 288 } 289 290 string16 name = display_name; 291 if (name.empty()) 292 name = volume_label; 293 if (name.empty()) 294 name = GetFullProductName(vendor_name, model_name); 295 if (name.empty()) 296 name = l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNLABELED_DEVICE); 297 298 name = GetDisplayNameForDevice(total_size_in_bytes, name); 299 300 if (!path.empty()) 301 name = GetDisplayNameForSubFolder(name, path); 302 303 return name; 304 } 305 306 string16 MediaGalleryPrefInfo::GetGalleryTooltip() const { 307 return AbsolutePath().LossyDisplayName(); 308 } 309 310 string16 MediaGalleryPrefInfo::GetGalleryAdditionalDetails() const { 311 string16 attached; 312 if (StorageInfo::IsRemovableDevice(device_id)) { 313 if (MediaStorageUtil::IsRemovableStorageAttached(device_id)) { 314 attached = l10n_util::GetStringUTF16( 315 IDS_MEDIA_GALLERIES_DIALOG_DEVICE_ATTACHED); 316 } else if (!last_attach_time.is_null()) { 317 attached = l10n_util::GetStringFUTF16( 318 IDS_MEDIA_GALLERIES_LAST_ATTACHED, 319 base::TimeFormatShortDateNumeric(last_attach_time)); 320 } else { 321 attached = l10n_util::GetStringUTF16( 322 IDS_MEDIA_GALLERIES_DIALOG_DEVICE_NOT_ATTACHED); 323 } 324 } 325 326 return attached; 327 } 328 329 bool MediaGalleryPrefInfo::IsGalleryAvailable() const { 330 return !StorageInfo::IsRemovableDevice(device_id) || 331 MediaStorageUtil::IsRemovableStorageAttached(device_id); 332 } 333 334 MediaGalleriesPreferences::GalleryChangeObserver::~GalleryChangeObserver() {} 335 336 MediaGalleriesPreferences::MediaGalleriesPreferences(Profile* profile) 337 : weak_factory_(this), 338 profile_(profile), 339 extension_prefs_for_testing_(NULL) { 340 AddDefaultGalleriesIfFreshProfile(); 341 342 // Look for optional default galleries every time. 343 itunes::ITunesFinder::FindITunesLibrary( 344 base::Bind(&MediaGalleriesPreferences::OnITunesDeviceID, 345 weak_factory_.GetWeakPtr())); 346 347 // TODO(tommycli): Turn on when Picasa code is ready. 348 #if 0 349 picasa::PicasaFinder::FindPicasaDatabaseOnUIThread( 350 base::Bind(&MediaGalleriesPreferences::OnPicasaDeviceID, 351 weak_factory_.GetWeakPtr())); 352 #endif 353 354 InitFromPrefs(false /*no notification*/); 355 356 StorageMonitor::GetInstance()->AddObserver(this); 357 } 358 359 MediaGalleriesPreferences::~MediaGalleriesPreferences() { 360 if (StorageMonitor::GetInstance()) 361 StorageMonitor::GetInstance()->RemoveObserver(this); 362 } 363 364 void MediaGalleriesPreferences::AddDefaultGalleriesIfFreshProfile() { 365 // Only add defaults the first time. 366 if (APIHasBeenUsed(profile_)) 367 return; 368 369 // Fresh profile case. 370 const int kDirectoryKeys[] = { 371 DIR_USER_MUSIC, 372 DIR_USER_PICTURES, 373 DIR_USER_VIDEOS, 374 }; 375 376 for (size_t i = 0; i < arraysize(kDirectoryKeys); ++i) { 377 base::FilePath path; 378 if (!PathService::Get(kDirectoryKeys[i], &path)) 379 continue; 380 381 base::FilePath relative_path; 382 StorageInfo info; 383 if (MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) { 384 AddGalleryInternal(info.device_id(), info.name(), relative_path, false, 385 info.storage_label(), info.vendor_name(), 386 info.model_name(), info.total_size_in_bytes(), 387 base::Time(), true, 2); 388 } 389 } 390 } 391 392 bool MediaGalleriesPreferences::UpdateDeviceIDForSingletonType( 393 const std::string& device_id) { 394 StorageInfo::Type singleton_type; 395 if (!StorageInfo::CrackDeviceId(device_id, &singleton_type, NULL)) 396 return false; 397 398 PrefService* prefs = profile_->GetPrefs(); 399 scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate( 400 prefs, prefs::kMediaGalleriesRememberedGalleries)); 401 ListValue* list = update->Get(); 402 for (ListValue::iterator iter = list->begin(); iter != list->end(); ++iter) { 403 // All of these calls should succeed, but preferences file can be corrupt. 404 DictionaryValue* dict; 405 if (!(*iter)->GetAsDictionary(&dict)) 406 continue; 407 std::string this_device_id; 408 if (!dict->GetString(kMediaGalleriesDeviceIdKey, &this_device_id)) 409 continue; 410 if (this_device_id == device_id) 411 return true; // No update is necessary. 412 StorageInfo::Type device_type; 413 if (!StorageInfo::CrackDeviceId(this_device_id, &device_type, NULL)) 414 continue; 415 416 if (device_type == singleton_type) { 417 dict->SetString(kMediaGalleriesDeviceIdKey, device_id); 418 update.reset(); // commits the update. 419 InitFromPrefs(true /* notify observers */); 420 return true; 421 } 422 } 423 return false; 424 } 425 426 void MediaGalleriesPreferences::OnITunesDeviceID(const std::string& device_id) { 427 if (device_id.empty()) 428 return; 429 if (!UpdateDeviceIDForSingletonType(device_id)) { 430 AddGalleryInternal(device_id, ASCIIToUTF16(kITunesGalleryName), 431 base::FilePath(), false /*not user added*/, 432 string16(), string16(), string16(), 0, 433 base::Time(), false, 2); 434 } 435 } 436 437 void MediaGalleriesPreferences::OnPicasaDeviceID(const std::string& device_id) { 438 DCHECK(!device_id.empty()); 439 if (!UpdateDeviceIDForSingletonType(device_id)) { 440 AddGalleryInternal(device_id, ASCIIToUTF16(kPicasaGalleryName), 441 base::FilePath(), false /*not user added*/, 442 string16(), string16(), string16(), 0, 443 base::Time(), false, 2); 444 } 445 } 446 447 void MediaGalleriesPreferences::InitFromPrefs(bool notify_observers) { 448 known_galleries_.clear(); 449 device_map_.clear(); 450 451 PrefService* prefs = profile_->GetPrefs(); 452 const ListValue* list = prefs->GetList( 453 prefs::kMediaGalleriesRememberedGalleries); 454 if (list) { 455 for (ListValue::const_iterator it = list->begin(); 456 it != list->end(); ++it) { 457 const DictionaryValue* dict = NULL; 458 if (!(*it)->GetAsDictionary(&dict)) 459 continue; 460 461 MediaGalleryPrefInfo gallery_info; 462 if (!PopulateGalleryPrefInfoFromDictionary(*dict, &gallery_info)) 463 continue; 464 465 known_galleries_[gallery_info.pref_id] = gallery_info; 466 device_map_[gallery_info.device_id].insert(gallery_info.pref_id); 467 } 468 } 469 if (notify_observers) 470 NotifyChangeObservers(std::string(), kInvalidMediaGalleryPrefId, false); 471 } 472 473 void MediaGalleriesPreferences::NotifyChangeObservers( 474 const std::string& extension_id, 475 MediaGalleryPrefId pref_id, 476 bool has_permission) { 477 FOR_EACH_OBSERVER(GalleryChangeObserver, 478 gallery_change_observers_, 479 OnGalleryChanged(this, extension_id, pref_id, 480 has_permission)); 481 } 482 483 void MediaGalleriesPreferences::AddGalleryChangeObserver( 484 GalleryChangeObserver* observer) { 485 gallery_change_observers_.AddObserver(observer); 486 } 487 488 void MediaGalleriesPreferences::RemoveGalleryChangeObserver( 489 GalleryChangeObserver* observer) { 490 gallery_change_observers_.RemoveObserver(observer); 491 } 492 493 void MediaGalleriesPreferences::OnRemovableStorageAttached( 494 const StorageInfo& info) { 495 if (!StorageInfo::IsMediaDevice(info.device_id())) 496 return; 497 498 AddGallery(info.device_id(), base::FilePath(), 499 false /*not user added*/, 500 info.storage_label(), 501 info.vendor_name(), 502 info.model_name(), 503 info.total_size_in_bytes(), 504 base::Time::Now()); 505 } 506 507 bool MediaGalleriesPreferences::LookUpGalleryByPath( 508 const base::FilePath& path, 509 MediaGalleryPrefInfo* gallery_info) const { 510 StorageInfo info; 511 base::FilePath relative_path; 512 if (!MediaStorageUtil::GetDeviceInfoFromPath(path, &info, &relative_path)) { 513 if (gallery_info) 514 *gallery_info = MediaGalleryPrefInfo(); 515 return false; 516 } 517 518 relative_path = relative_path.NormalizePathSeparators(); 519 MediaGalleryPrefIdSet galleries_on_device = 520 LookUpGalleriesByDeviceId(info.device_id()); 521 for (MediaGalleryPrefIdSet::const_iterator it = galleries_on_device.begin(); 522 it != galleries_on_device.end(); 523 ++it) { 524 const MediaGalleryPrefInfo& gallery = known_galleries_.find(*it)->second; 525 if (gallery.path != relative_path) 526 continue; 527 528 if (gallery_info) 529 *gallery_info = gallery; 530 return true; 531 } 532 533 // This method is called by controller::FilesSelected when the user 534 // adds a new gallery. Control reaches here when the selected gallery is 535 // on a volume we know about, but have no gallery already for. Returns 536 // hypothetical data to the caller about what the prefs will look like 537 // if the gallery is added. 538 // TODO(gbillock): split this out into another function so it doesn't 539 // conflate LookUp. 540 if (gallery_info) { 541 gallery_info->pref_id = kInvalidMediaGalleryPrefId; 542 gallery_info->device_id = info.device_id(); 543 gallery_info->path = relative_path; 544 gallery_info->type = MediaGalleryPrefInfo::kUserAdded; 545 gallery_info->volume_label = info.storage_label(); 546 gallery_info->vendor_name = info.vendor_name(); 547 gallery_info->model_name = info.model_name(); 548 gallery_info->total_size_in_bytes = info.total_size_in_bytes(); 549 gallery_info->last_attach_time = base::Time::Now(); 550 gallery_info->volume_metadata_valid = true; 551 gallery_info->prefs_version = 2; 552 } 553 return false; 554 } 555 556 MediaGalleryPrefIdSet MediaGalleriesPreferences::LookUpGalleriesByDeviceId( 557 const std::string& device_id) const { 558 DeviceIdPrefIdsMap::const_iterator found = device_map_.find(device_id); 559 if (found == device_map_.end()) 560 return MediaGalleryPrefIdSet(); 561 return found->second; 562 } 563 564 base::FilePath MediaGalleriesPreferences::LookUpGalleryPathForExtension( 565 MediaGalleryPrefId gallery_id, 566 const extensions::Extension* extension, 567 bool include_unpermitted_galleries) { 568 DCHECK(extension); 569 if (!include_unpermitted_galleries && 570 !ContainsKey(GalleriesForExtension(*extension), gallery_id)) 571 return base::FilePath(); 572 573 MediaGalleriesPrefInfoMap::const_iterator it = 574 known_galleries_.find(gallery_id); 575 if (it == known_galleries_.end()) 576 return base::FilePath(); 577 return MediaStorageUtil::FindDevicePathById(it->second.device_id); 578 } 579 580 MediaGalleryPrefId MediaGalleriesPreferences::AddGallery( 581 const std::string& device_id, 582 const base::FilePath& relative_path, bool user_added, 583 const string16& volume_label, const string16& vendor_name, 584 const string16& model_name, uint64 total_size_in_bytes, 585 base::Time last_attach_time) { 586 return AddGalleryInternal(device_id, string16(), relative_path, user_added, 587 volume_label, vendor_name, model_name, 588 total_size_in_bytes, last_attach_time, true, 2); 589 } 590 591 MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryInternal( 592 const std::string& device_id, const string16& display_name, 593 const base::FilePath& relative_path, bool user_added, 594 const string16& volume_label, const string16& vendor_name, 595 const string16& model_name, uint64 total_size_in_bytes, 596 base::Time last_attach_time, 597 bool volume_metadata_valid, 598 int prefs_version) { 599 base::FilePath normalized_relative_path = 600 relative_path.NormalizePathSeparators(); 601 MediaGalleryPrefIdSet galleries_on_device = 602 LookUpGalleriesByDeviceId(device_id); 603 for (MediaGalleryPrefIdSet::const_iterator it = galleries_on_device.begin(); 604 it != galleries_on_device.end(); 605 ++it) { 606 const MediaGalleryPrefInfo& existing = known_galleries_.find(*it)->second; 607 if (existing.path != normalized_relative_path) 608 continue; 609 610 bool update_gallery_type = 611 user_added && (existing.type == MediaGalleryPrefInfo::kBlackListed); 612 // Status quo: In M27 and M28, galleries added manually use version 0, 613 // and galleries added automatically (including default galleries) use 614 // version 1. The name override is used by default galleries as well 615 // as all device attach events. 616 // We want to upgrade the name if the existing version is < 2. Leave it 617 // alone if the existing display name is set with version == 2 and the 618 // proposed new name is empty. 619 bool update_gallery_name = existing.display_name != display_name; 620 if (existing.prefs_version == 2 && !existing.display_name.empty() && 621 display_name.empty()) { 622 update_gallery_name = false; 623 } 624 bool update_gallery_metadata = volume_metadata_valid && 625 ((existing.volume_label != volume_label) || 626 (existing.vendor_name != vendor_name) || 627 (existing.model_name != model_name) || 628 (existing.total_size_in_bytes != total_size_in_bytes) || 629 (existing.last_attach_time != last_attach_time)); 630 631 if (!update_gallery_name && !update_gallery_type && 632 !update_gallery_metadata) 633 return *it; 634 635 PrefService* prefs = profile_->GetPrefs(); 636 scoped_ptr<ListPrefUpdate> update( 637 new ListPrefUpdate(prefs, prefs::kMediaGalleriesRememberedGalleries)); 638 ListValue* list = update->Get(); 639 640 for (ListValue::const_iterator list_iter = list->begin(); 641 list_iter != list->end(); 642 ++list_iter) { 643 DictionaryValue* dict; 644 MediaGalleryPrefId iter_id; 645 if ((*list_iter)->GetAsDictionary(&dict) && 646 GetPrefId(*dict, &iter_id) && 647 *it == iter_id) { 648 if (update_gallery_type) { 649 dict->SetString(kMediaGalleriesTypeKey, 650 kMediaGalleriesTypeAutoDetectedValue); 651 } 652 if (update_gallery_name) 653 dict->SetString(kMediaGalleriesDisplayNameKey, display_name); 654 if (update_gallery_metadata) { 655 dict->SetString(kMediaGalleriesVolumeLabelKey, volume_label); 656 dict->SetString(kMediaGalleriesVendorNameKey, vendor_name); 657 dict->SetString(kMediaGalleriesModelNameKey, model_name); 658 dict->SetDouble(kMediaGalleriesSizeKey, total_size_in_bytes); 659 dict->SetDouble(kMediaGalleriesLastAttachTimeKey, 660 last_attach_time.ToInternalValue()); 661 } 662 dict->SetInteger(kMediaGalleriesPrefsVersionKey, prefs_version); 663 break; 664 } 665 } 666 667 // Commits the prefs update. 668 update.reset(); 669 670 if (update_gallery_name || update_gallery_metadata || update_gallery_type) 671 InitFromPrefs(true /* notify observers */); 672 return *it; 673 } 674 675 PrefService* prefs = profile_->GetPrefs(); 676 677 MediaGalleryPrefInfo gallery_info; 678 gallery_info.pref_id = prefs->GetUint64(prefs::kMediaGalleriesUniqueId); 679 prefs->SetUint64(prefs::kMediaGalleriesUniqueId, gallery_info.pref_id + 1); 680 gallery_info.display_name = display_name; 681 gallery_info.device_id = device_id; 682 gallery_info.path = normalized_relative_path; 683 gallery_info.type = MediaGalleryPrefInfo::kAutoDetected; 684 if (user_added) 685 gallery_info.type = MediaGalleryPrefInfo::kUserAdded; 686 if (volume_metadata_valid) { 687 gallery_info.volume_label = volume_label; 688 gallery_info.vendor_name = vendor_name; 689 gallery_info.model_name = model_name; 690 gallery_info.total_size_in_bytes = total_size_in_bytes; 691 gallery_info.last_attach_time = last_attach_time; 692 } 693 gallery_info.volume_metadata_valid = volume_metadata_valid; 694 gallery_info.prefs_version = prefs_version; 695 696 { 697 ListPrefUpdate update(prefs, prefs::kMediaGalleriesRememberedGalleries); 698 ListValue* list = update.Get(); 699 list->Append(CreateGalleryPrefInfoDictionary(gallery_info)); 700 } 701 InitFromPrefs(true /* notify observers */); 702 703 return gallery_info.pref_id; 704 } 705 706 MediaGalleryPrefId MediaGalleriesPreferences::AddGalleryByPath( 707 const base::FilePath& path) { 708 MediaGalleryPrefInfo gallery_info; 709 if (LookUpGalleryByPath(path, &gallery_info) && 710 gallery_info.type != MediaGalleryPrefInfo::kBlackListed) { 711 return gallery_info.pref_id; 712 } 713 return AddGalleryInternal(gallery_info.device_id, 714 gallery_info.display_name, 715 gallery_info.path, 716 true /*user added*/, 717 gallery_info.volume_label, 718 gallery_info.vendor_name, 719 gallery_info.model_name, 720 gallery_info.total_size_in_bytes, 721 gallery_info.last_attach_time, 722 gallery_info.volume_metadata_valid, 723 gallery_info.prefs_version); 724 } 725 726 void MediaGalleriesPreferences::ForgetGalleryById(MediaGalleryPrefId pref_id) { 727 PrefService* prefs = profile_->GetPrefs(); 728 scoped_ptr<ListPrefUpdate> update(new ListPrefUpdate( 729 prefs, prefs::kMediaGalleriesRememberedGalleries)); 730 ListValue* list = update->Get(); 731 732 if (!ContainsKey(known_galleries_, pref_id)) 733 return; 734 735 for (ListValue::iterator iter = list->begin(); iter != list->end(); ++iter) { 736 DictionaryValue* dict; 737 MediaGalleryPrefId iter_id; 738 if ((*iter)->GetAsDictionary(&dict) && GetPrefId(*dict, &iter_id) && 739 pref_id == iter_id) { 740 RemoveGalleryPermissionsFromPrefs(pref_id); 741 MediaGalleryPrefInfo::Type type; 742 if (GetType(*dict, &type) && 743 type == MediaGalleryPrefInfo::kAutoDetected) { 744 dict->SetString(kMediaGalleriesTypeKey, 745 kMediaGalleriesTypeBlackListedValue); 746 } else { 747 list->Erase(iter, NULL); 748 } 749 update.reset(NULL); // commits the update. 750 751 InitFromPrefs(true /* notify observers */); 752 return; 753 } 754 } 755 } 756 757 MediaGalleryPrefIdSet MediaGalleriesPreferences::GalleriesForExtension( 758 const extensions::Extension& extension) const { 759 MediaGalleryPrefIdSet result; 760 761 if (HasAutoDetectedGalleryPermission(extension)) { 762 for (MediaGalleriesPrefInfoMap::const_iterator it = 763 known_galleries_.begin(); it != known_galleries_.end(); ++it) { 764 if (it->second.type == MediaGalleryPrefInfo::kAutoDetected) 765 result.insert(it->second.pref_id); 766 } 767 } 768 769 std::vector<MediaGalleryPermission> stored_permissions = 770 GetGalleryPermissionsFromPrefs(extension.id()); 771 for (std::vector<MediaGalleryPermission>::const_iterator it = 772 stored_permissions.begin(); it != stored_permissions.end(); ++it) { 773 if (!it->has_permission) { 774 result.erase(it->pref_id); 775 } else { 776 MediaGalleriesPrefInfoMap::const_iterator gallery = 777 known_galleries_.find(it->pref_id); 778 DCHECK(gallery != known_galleries_.end()); 779 if (gallery->second.type != MediaGalleryPrefInfo::kBlackListed) { 780 result.insert(it->pref_id); 781 } else { 782 NOTREACHED() << gallery->second.device_id; 783 } 784 } 785 } 786 return result; 787 } 788 789 void MediaGalleriesPreferences::SetGalleryPermissionForExtension( 790 const extensions::Extension& extension, 791 MediaGalleryPrefId pref_id, 792 bool has_permission) { 793 // The gallery may not exist anymore if the user opened a second config 794 // surface concurrently and removed it. Drop the permission update if so. 795 MediaGalleriesPrefInfoMap::const_iterator gallery_info = 796 known_galleries_.find(pref_id); 797 if (gallery_info == known_galleries_.end()) 798 return; 799 800 bool all_permission = HasAutoDetectedGalleryPermission(extension); 801 if (has_permission && all_permission) { 802 if (gallery_info->second.type == MediaGalleryPrefInfo::kAutoDetected) { 803 UnsetGalleryPermissionInPrefs(extension.id(), pref_id); 804 NotifyChangeObservers(extension.id(), pref_id, true); 805 return; 806 } 807 } 808 809 if (!has_permission && !all_permission) { 810 UnsetGalleryPermissionInPrefs(extension.id(), pref_id); 811 } else { 812 SetGalleryPermissionInPrefs(extension.id(), pref_id, has_permission); 813 } 814 NotifyChangeObservers(extension.id(), pref_id, has_permission); 815 } 816 817 void MediaGalleriesPreferences::Shutdown() { 818 weak_factory_.InvalidateWeakPtrs(); 819 profile_ = NULL; 820 } 821 822 // static 823 bool MediaGalleriesPreferences::APIHasBeenUsed(Profile* profile) { 824 MediaGalleryPrefId current_id = 825 profile->GetPrefs()->GetUint64(prefs::kMediaGalleriesUniqueId); 826 return current_id != kInvalidMediaGalleryPrefId + 1; 827 } 828 829 // static 830 void MediaGalleriesPreferences::RegisterProfilePrefs( 831 user_prefs::PrefRegistrySyncable* registry) { 832 registry->RegisterListPref(prefs::kMediaGalleriesRememberedGalleries, 833 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 834 registry->RegisterUint64Pref( 835 prefs::kMediaGalleriesUniqueId, 836 kInvalidMediaGalleryPrefId + 1, 837 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 838 } 839 840 void MediaGalleriesPreferences::SetGalleryPermissionInPrefs( 841 const std::string& extension_id, 842 MediaGalleryPrefId gallery_id, 843 bool has_access) { 844 ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(), 845 extension_id, 846 kMediaGalleriesPermissions); 847 ListValue* permissions = update.Get(); 848 if (!permissions) { 849 permissions = update.Create(); 850 } else { 851 // If the gallery is already in the list, update the permission... 852 for (ListValue::iterator iter = permissions->begin(); 853 iter != permissions->end(); ++iter) { 854 DictionaryValue* dict = NULL; 855 if (!(*iter)->GetAsDictionary(&dict)) 856 continue; 857 MediaGalleryPermission perm; 858 if (!GetMediaGalleryPermissionFromDictionary(dict, &perm)) 859 continue; 860 if (perm.pref_id == gallery_id) { 861 dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access); 862 return; 863 } 864 } 865 } 866 // ...Otherwise, add a new entry for the gallery. 867 DictionaryValue* dict = new DictionaryValue; 868 dict->SetString(kMediaGalleryIdKey, base::Uint64ToString(gallery_id)); 869 dict->SetBoolean(kMediaGalleryHasPermissionKey, has_access); 870 permissions->Append(dict); 871 } 872 873 void MediaGalleriesPreferences::UnsetGalleryPermissionInPrefs( 874 const std::string& extension_id, 875 MediaGalleryPrefId gallery_id) { 876 ExtensionPrefs::ScopedListUpdate update(GetExtensionPrefs(), 877 extension_id, 878 kMediaGalleriesPermissions); 879 ListValue* permissions = update.Get(); 880 if (!permissions) 881 return; 882 883 for (ListValue::iterator iter = permissions->begin(); 884 iter != permissions->end(); ++iter) { 885 const DictionaryValue* dict = NULL; 886 if (!(*iter)->GetAsDictionary(&dict)) 887 continue; 888 MediaGalleryPermission perm; 889 if (!GetMediaGalleryPermissionFromDictionary(dict, &perm)) 890 continue; 891 if (perm.pref_id == gallery_id) { 892 permissions->Erase(iter, NULL); 893 return; 894 } 895 } 896 } 897 898 std::vector<MediaGalleryPermission> 899 MediaGalleriesPreferences::GetGalleryPermissionsFromPrefs( 900 const std::string& extension_id) const { 901 std::vector<MediaGalleryPermission> result; 902 const ListValue* permissions; 903 if (!GetExtensionPrefs()->ReadPrefAsList(extension_id, 904 kMediaGalleriesPermissions, 905 &permissions)) { 906 return result; 907 } 908 909 for (ListValue::const_iterator iter = permissions->begin(); 910 iter != permissions->end(); ++iter) { 911 DictionaryValue* dict = NULL; 912 if (!(*iter)->GetAsDictionary(&dict)) 913 continue; 914 MediaGalleryPermission perm; 915 if (!GetMediaGalleryPermissionFromDictionary(dict, &perm)) 916 continue; 917 result.push_back(perm); 918 } 919 920 return result; 921 } 922 923 void MediaGalleriesPreferences::RemoveGalleryPermissionsFromPrefs( 924 MediaGalleryPrefId gallery_id) { 925 ExtensionPrefs* prefs = GetExtensionPrefs(); 926 const DictionaryValue* extensions = 927 prefs->pref_service()->GetDictionary(ExtensionPrefs::kExtensionsPref); 928 if (!extensions) 929 return; 930 931 for (DictionaryValue::Iterator iter(*extensions); !iter.IsAtEnd(); 932 iter.Advance()) { 933 if (!extensions::Extension::IdIsValid(iter.key())) { 934 NOTREACHED(); 935 continue; 936 } 937 UnsetGalleryPermissionInPrefs(iter.key(), gallery_id); 938 } 939 } 940 941 ExtensionPrefs* MediaGalleriesPreferences::GetExtensionPrefs() const { 942 if (extension_prefs_for_testing_) 943 return extension_prefs_for_testing_; 944 return extensions::ExtensionPrefs::Get(profile_); 945 } 946 947 void MediaGalleriesPreferences::SetExtensionPrefsForTesting( 948 extensions::ExtensionPrefs* extension_prefs) { 949 extension_prefs_for_testing_ = extension_prefs; 950 } 951 952 } // namespace chrome 953