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