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_dialog_controller.h"
6
7#include "base/path_service.h"
8#include "base/stl_util.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/browser_process.h"
11#include "chrome/browser/media_galleries/media_file_system_registry.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/storage_monitor/storage_info.h"
14#include "chrome/browser/storage_monitor/storage_monitor.h"
15#include "chrome/browser/ui/chrome_select_file_policy.h"
16#include "chrome/common/chrome_paths.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/permissions/media_galleries_permission.h"
19#include "chrome/common/extensions/permissions/permissions_data.h"
20#include "content/public/browser/web_contents.h"
21#include "content/public/browser/web_contents_view.h"
22#include "grit/generated_resources.h"
23#include "ui/base/l10n/l10n_util.h"
24#include "ui/base/text/bytes_formatting.h"
25
26using extensions::APIPermission;
27using extensions::Extension;
28
29namespace chrome {
30
31namespace {
32
33// Comparator for sorting GalleryPermissionsVector -- sorts
34// allowed galleries low, and then sorts by absolute path.
35bool GalleriesVectorComparator(
36    const MediaGalleriesDialogController::GalleryPermission& a,
37    const MediaGalleriesDialogController::GalleryPermission& b) {
38  if (a.allowed && !b.allowed)
39    return true;
40  if (!a.allowed && b.allowed)
41    return false;
42
43  return a.pref_info.AbsolutePath() < b.pref_info.AbsolutePath();
44}
45
46}  // namespace
47
48MediaGalleriesDialogController::MediaGalleriesDialogController(
49    content::WebContents* web_contents,
50    const Extension& extension,
51    const base::Closure& on_finish)
52      : web_contents_(web_contents),
53        extension_(&extension),
54        on_finish_(on_finish) {
55  // Passing unretained pointer is safe, since the dialog controller
56  // is self-deleting, and so won't be deleted until it can be shown
57  // and then closed.
58  StorageMonitor::GetInstance()->EnsureInitialized(base::Bind(
59      &MediaGalleriesDialogController::OnStorageMonitorInitialized,
60      base::Unretained(this)));
61}
62
63void MediaGalleriesDialogController::OnStorageMonitorInitialized() {
64  MediaFileSystemRegistry* registry =
65      g_browser_process->media_file_system_registry();
66  preferences_ = registry->GetPreferences(
67      Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
68  InitializePermissions();
69
70  dialog_.reset(MediaGalleriesDialog::Create(this));
71
72  StorageMonitor::GetInstance()->AddObserver(this);
73
74  preferences_->AddGalleryChangeObserver(this);
75}
76
77MediaGalleriesDialogController::MediaGalleriesDialogController(
78    const extensions::Extension& extension)
79    : web_contents_(NULL),
80      extension_(&extension),
81      preferences_(NULL) {}
82
83MediaGalleriesDialogController::~MediaGalleriesDialogController() {
84  if (chrome::StorageMonitor::GetInstance())
85    StorageMonitor::GetInstance()->RemoveObserver(this);
86
87  if (select_folder_dialog_.get())
88    select_folder_dialog_->ListenerDestroyed();
89}
90
91string16 MediaGalleriesDialogController::GetHeader() const {
92  return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_HEADER);
93}
94
95string16 MediaGalleriesDialogController::GetSubtext() const {
96  extensions::MediaGalleriesPermission::CheckParam read_param(
97      extensions::MediaGalleriesPermission::kReadPermission);
98  extensions::MediaGalleriesPermission::CheckParam copy_to_param(
99      extensions::MediaGalleriesPermission::kCopyToPermission);
100  bool has_read_permission =
101      extensions::PermissionsData::CheckAPIPermissionWithParam(
102          extension_, APIPermission::kMediaGalleries, &read_param);
103  bool has_copy_to_permission =
104      extensions::PermissionsData::CheckAPIPermissionWithParam(
105          extension_, APIPermission::kMediaGalleries, &copy_to_param);
106
107  int id;
108  if (has_read_permission && has_copy_to_permission)
109    id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_WRITE;
110  else if (has_copy_to_permission)
111    id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_WRITE_ONLY;
112  else
113    id = IDS_MEDIA_GALLERIES_DIALOG_SUBTEXT_READ_ONLY;
114
115  return l10n_util::GetStringFUTF16(id, UTF8ToUTF16(extension_->name()));
116}
117
118string16 MediaGalleriesDialogController::GetUnattachedLocationsHeader() const {
119  return l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_UNATTACHED_LOCATIONS);
120}
121
122// TODO(gbillock): Call this something a bit more connected to the
123// messaging in the dialog.
124bool MediaGalleriesDialogController::HasPermittedGalleries() const {
125  for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
126       iter != known_galleries_.end(); ++iter) {
127    if (iter->second.allowed)
128      return true;
129  }
130
131  // Do this? Views did.
132  if (new_galleries_.size() > 0)
133    return true;
134
135  return false;
136}
137
138// Note: sorts by display criterion: GalleriesVectorComparator.
139void MediaGalleriesDialogController::FillPermissions(
140    bool attached,
141    MediaGalleriesDialogController::GalleryPermissionsVector* permissions)
142    const {
143  for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
144       iter != known_galleries_.end(); ++iter) {
145    if ((attached && iter->second.pref_info.IsGalleryAvailable()) ||
146        (!attached && !iter->second.pref_info.IsGalleryAvailable())) {
147      permissions->push_back(iter->second);
148    }
149  }
150  for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
151       iter != new_galleries_.end(); ++iter) {
152    if ((attached && iter->pref_info.IsGalleryAvailable()) ||
153        (!attached && !iter->pref_info.IsGalleryAvailable())) {
154      permissions->push_back(*iter);
155    }
156  }
157
158  std::sort(permissions->begin(), permissions->end(),
159            GalleriesVectorComparator);
160}
161
162MediaGalleriesDialogController::GalleryPermissionsVector
163MediaGalleriesDialogController::AttachedPermissions() const {
164  GalleryPermissionsVector attached;
165  FillPermissions(true, &attached);
166  return attached;
167}
168
169MediaGalleriesDialogController::GalleryPermissionsVector
170MediaGalleriesDialogController::UnattachedPermissions() const {
171  GalleryPermissionsVector unattached;
172  FillPermissions(false, &unattached);
173  return unattached;
174}
175
176void MediaGalleriesDialogController::OnAddFolderClicked() {
177  base::FilePath user_data_dir;
178  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
179  select_folder_dialog_ =
180      ui::SelectFileDialog::Create(this, new ChromeSelectFilePolicy(NULL));
181  select_folder_dialog_->SelectFile(
182      ui::SelectFileDialog::SELECT_FOLDER,
183      l10n_util::GetStringUTF16(IDS_MEDIA_GALLERIES_DIALOG_ADD_GALLERY_TITLE),
184      user_data_dir,
185      NULL,
186      0,
187      base::FilePath::StringType(),
188      web_contents_->GetView()->GetTopLevelNativeWindow(),
189      NULL);
190}
191
192void MediaGalleriesDialogController::DidToggleGalleryId(
193    MediaGalleryPrefId gallery_id,
194    bool enabled) {
195  // Check known galleries.
196  KnownGalleryPermissions::iterator iter =
197      known_galleries_.find(gallery_id);
198  if (iter != known_galleries_.end()) {
199    if (iter->second.allowed == enabled)
200      return;
201
202    iter->second.allowed = enabled;
203    if (ContainsKey(toggled_galleries_, gallery_id))
204      toggled_galleries_.erase(gallery_id);
205    else
206      toggled_galleries_.insert(gallery_id);
207    return;
208  }
209
210  // Check new galleries.
211  for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
212       iter != new_galleries_.end(); ++iter) {
213    if (iter->pref_info.pref_id == gallery_id) {
214      iter->allowed = enabled;
215      return;
216    }
217  }
218
219  // Don't sort -- the dialog is open, and we don't want to adjust any
220  // positions for future updates to the dialog contents until they are
221  // redrawn.
222}
223
224void MediaGalleriesDialogController::DialogFinished(bool accepted) {
225  // The dialog has finished, so there is no need to watch for more updates
226  // from |preferences_|. Do this here and not in the dtor since this is the
227  // only non-test code path that deletes |this|. The test ctor never adds
228  // this observer in the first place.
229  preferences_->RemoveGalleryChangeObserver(this);
230
231  if (accepted)
232    SavePermissions();
233
234  on_finish_.Run();
235  delete this;
236}
237
238content::WebContents* MediaGalleriesDialogController::web_contents() {
239  return web_contents_;
240}
241
242void MediaGalleriesDialogController::FileSelected(const base::FilePath& path,
243                                                  int /*index*/,
244                                                  void* /*params*/) {
245  // Try to find it in the prefs.
246  MediaGalleryPrefInfo gallery;
247  bool gallery_exists = preferences_->LookUpGalleryByPath(path, &gallery);
248  if (gallery_exists && gallery.type != MediaGalleryPrefInfo::kBlackListed) {
249    // The prefs are in sync with |known_galleries_|, so it should exist in
250    // |known_galleries_| as well. User selecting a known gallery effectively
251    // just sets the gallery to permitted.
252    KnownGalleryPermissions::const_iterator iter =
253        known_galleries_.find(gallery.pref_id);
254    DCHECK(iter != known_galleries_.end());
255    dialog_->UpdateGallery(iter->second.pref_info, true);
256    return;
257  }
258
259  // Try to find it in |new_galleries_| (user added same folder twice).
260  for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
261       iter != new_galleries_.end(); ++iter) {
262    if (iter->pref_info.path == gallery.path &&
263        iter->pref_info.device_id == gallery.device_id) {
264      iter->allowed = true;
265      dialog_->UpdateGallery(iter->pref_info, true);
266      return;
267    }
268  }
269
270  // Lastly, add a new gallery to |new_galleries_|.
271  new_galleries_.push_back(GalleryPermission(gallery, true));
272  dialog_->UpdateGallery(new_galleries_.back().pref_info, true);
273}
274
275void MediaGalleriesDialogController::OnRemovableStorageAttached(
276    const StorageInfo& info) {
277  UpdateGalleriesOnDeviceEvent(info.device_id());
278}
279
280void MediaGalleriesDialogController::OnRemovableStorageDetached(
281    const StorageInfo& info) {
282  UpdateGalleriesOnDeviceEvent(info.device_id());
283}
284
285void MediaGalleriesDialogController::OnGalleryChanged(
286    MediaGalleriesPreferences* pref, const std::string& extension_id,
287    MediaGalleryPrefId /* pref_id */, bool /* has_permission */) {
288  DCHECK_EQ(preferences_, pref);
289  if (extension_id.empty() || extension_id == extension_->id())
290    UpdateGalleriesOnPreferencesEvent();
291}
292
293void MediaGalleriesDialogController::InitializePermissions() {
294  const MediaGalleriesPrefInfoMap& galleries = preferences_->known_galleries();
295  for (MediaGalleriesPrefInfoMap::const_iterator iter = galleries.begin();
296       iter != galleries.end();
297       ++iter) {
298    const MediaGalleryPrefInfo& gallery = iter->second;
299    if (gallery.type == MediaGalleryPrefInfo::kBlackListed)
300      continue;
301
302    known_galleries_[iter->first] = GalleryPermission(gallery, false);
303  }
304
305  MediaGalleryPrefIdSet permitted =
306      preferences_->GalleriesForExtension(*extension_);
307
308  for (MediaGalleryPrefIdSet::iterator iter = permitted.begin();
309       iter != permitted.end(); ++iter) {
310    if (ContainsKey(toggled_galleries_, *iter))
311      continue;
312    DCHECK(ContainsKey(known_galleries_, *iter));
313    known_galleries_[*iter].allowed = true;
314  }
315}
316
317void MediaGalleriesDialogController::SavePermissions() {
318  for (KnownGalleryPermissions::const_iterator iter = known_galleries_.begin();
319       iter != known_galleries_.end(); ++iter) {
320    preferences_->SetGalleryPermissionForExtension(
321        *extension_, iter->first, iter->second.allowed);
322  }
323
324  for (GalleryPermissionsVector::const_iterator iter = new_galleries_.begin();
325       iter != new_galleries_.end(); ++iter) {
326    // If the user added a gallery then unchecked it, forget about it.
327    if (!iter->allowed)
328      continue;
329
330    // TODO(gbillock): Should be adding volume metadata during FileSelected.
331    const MediaGalleryPrefInfo& gallery = iter->pref_info;
332    MediaGalleryPrefId id = preferences_->AddGallery(
333        gallery.device_id, gallery.path, true,
334        gallery.volume_label, gallery.vendor_name, gallery.model_name,
335        gallery.total_size_in_bytes, gallery.last_attach_time);
336    preferences_->SetGalleryPermissionForExtension(*extension_, id, true);
337  }
338}
339
340void MediaGalleriesDialogController::UpdateGalleriesOnPreferencesEvent() {
341  // Merge in the permissions from |preferences_|. Afterwards,
342  // |known_galleries_| may contain galleries that no longer belong there,
343  // but the code below will put |known_galleries_| back in a consistent state.
344  InitializePermissions();
345
346  // If a gallery no longer belongs in |known_galleries_|, forget it in the
347  // model/view.
348  // If a gallery still belong in |known_galleries_|, check for a duplicate
349  // entry in |new_galleries_|, merge its permission and remove it. Then update
350  // the view.
351  const MediaGalleriesPrefInfoMap& pref_galleries =
352      preferences_->known_galleries();
353  MediaGalleryPrefIdSet galleries_to_forget;
354  for (KnownGalleryPermissions::iterator it = known_galleries_.begin();
355       it != known_galleries_.end();
356       ++it) {
357    const MediaGalleryPrefId& gallery_id = it->first;
358    GalleryPermission& gallery = it->second;
359    MediaGalleriesPrefInfoMap::const_iterator pref_it =
360        pref_galleries.find(gallery_id);
361    // Check for lingering entry that should be removed.
362    if (pref_it == pref_galleries.end() ||
363        pref_it->second.type == MediaGalleryPrefInfo::kBlackListed) {
364      galleries_to_forget.insert(gallery_id);
365      dialog_->ForgetGallery(gallery.pref_info.pref_id);
366      continue;
367    }
368
369    // Look for duplicate entries in |new_galleries_|.
370    for (GalleryPermissionsVector::iterator new_it = new_galleries_.begin();
371         new_it != new_galleries_.end();
372         ++new_it) {
373      if (new_it->pref_info.path == gallery.pref_info.path &&
374          new_it->pref_info.device_id == gallery.pref_info.device_id) {
375        // Found duplicate entry. Get the existing permission from it and then
376        // remove it.
377        gallery.allowed = new_it->allowed;
378        dialog_->ForgetGallery(new_it->pref_info.pref_id);
379        new_galleries_.erase(new_it);
380        break;
381      }
382    }
383    dialog_->UpdateGallery(gallery.pref_info, gallery.allowed);
384  }
385
386  // Remove the galleries to forget from |known_galleries_|. Doing it in the
387  // above loop would invalidate the iterator there.
388  for (MediaGalleryPrefIdSet::const_iterator it = galleries_to_forget.begin();
389       it != galleries_to_forget.end();
390       ++it) {
391    known_galleries_.erase(*it);
392  }
393}
394
395void MediaGalleriesDialogController::UpdateGalleriesOnDeviceEvent(
396    const std::string& device_id) {
397  for (KnownGalleryPermissions::iterator iter = known_galleries_.begin();
398       iter != known_galleries_.end(); ++iter) {
399    if (iter->second.pref_info.device_id == device_id)
400      dialog_->UpdateGallery(iter->second.pref_info, iter->second.allowed);
401  }
402
403  for (GalleryPermissionsVector::iterator iter = new_galleries_.begin();
404       iter != new_galleries_.end(); ++iter) {
405    if (iter->pref_info.device_id == device_id)
406      dialog_->UpdateGallery(iter->pref_info, iter->allowed);
407  }
408}
409
410// MediaGalleries dialog -------------------------------------------------------
411
412MediaGalleriesDialog::~MediaGalleriesDialog() {}
413
414}  // namespace chrome
415