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// GalleryWatchStateTracker implementation.
6
7#include "chrome/browser/extensions/api/media_galleries_private/gallery_watch_state_tracker.h"
8
9#include "base/bind.h"
10#include "base/files/file_path.h"
11#include "base/location.h"
12#include "base/stl_util.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/values.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/extensions/api/media_galleries_private/gallery_watch_manager.h"
17#include "chrome/browser/extensions/api/media_galleries_private/media_galleries_private_api.h"
18#include "chrome/browser/extensions/api/media_galleries_private/media_galleries_private_event_router.h"
19#include "chrome/browser/media_galleries/media_file_system_registry.h"
20#include "chrome/browser/media_galleries/media_galleries_preferences.h"
21#include "chrome/browser/profiles/profile.h"
22#include "content/public/browser/browser_thread.h"
23#include "extensions/browser/extension_registry.h"
24#include "extensions/browser/extension_system.h"
25#include "extensions/browser/state_store.h"
26#include "extensions/common/extension.h"
27
28namespace extensions {
29
30namespace {
31
32// State store key to track the registered gallery watchers for the extensions.
33const char kRegisteredGalleryWatchers[] = "media_gallery_watchers";
34
35// Converts the storage |list| value to WatchedGalleryIds.
36MediaGalleryPrefIdSet WatchedGalleryIdsFromValue(
37    const base::ListValue* list) {
38  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
39  MediaGalleryPrefIdSet gallery_ids;
40  std::string gallery_id_str;
41  for (size_t i = 0; i < list->GetSize(); ++i) {
42    if (!list->GetString(i, &gallery_id_str) || gallery_id_str.empty())
43      continue;
44    MediaGalleryPrefId gallery_id;
45    if (base::StringToUint64(gallery_id_str, &gallery_id))
46      gallery_ids.insert(gallery_id);
47  }
48  return gallery_ids;
49}
50
51// Converts WatchedGalleryIds to a storage list value.
52scoped_ptr<base::ListValue> WatchedGalleryIdsToValue(
53    const MediaGalleryPrefIdSet gallery_ids) {
54  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
55  scoped_ptr<base::ListValue> list(new base::ListValue());
56  for (MediaGalleryPrefIdSet::const_iterator id_iter = gallery_ids.begin();
57       id_iter != gallery_ids.end(); ++id_iter)
58    list->AppendString(base::Uint64ToString(*id_iter));
59  return list.Pass();
60}
61
62// Looks up an extension by ID. Does not include disabled extensions.
63const Extension* GetExtensionById(Profile* profile,
64                                  const std::string& extension_id) {
65  return ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
66      extension_id);
67}
68
69}  // namespace
70
71GalleryWatchStateTracker::GalleryWatchStateTracker(Profile* profile)
72    : profile_(profile), extension_registry_observer_(this) {
73  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
74  DCHECK(profile_);
75  extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
76  MediaGalleriesPreferences* preferences =
77      g_browser_process->media_file_system_registry()->GetPreferences(profile);
78  preferences->AddGalleryChangeObserver(this);
79}
80
81GalleryWatchStateTracker::~GalleryWatchStateTracker() {
82  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
83  MediaGalleriesPreferences* preferences =
84      g_browser_process->media_file_system_registry()->GetPreferences(profile_);
85  preferences->RemoveGalleryChangeObserver(this);
86}
87
88// static
89GalleryWatchStateTracker* GalleryWatchStateTracker::GetForProfile(
90    Profile* profile) {
91  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
92  DCHECK(profile);
93  MediaGalleriesPrivateAPI* private_api =
94      MediaGalleriesPrivateAPI::Get(profile);
95  // In unit tests, we don't have a MediaGalleriesPrivateAPI.
96  if (private_api)
97    return private_api->GetGalleryWatchStateTracker();
98  return NULL;
99}
100
101void GalleryWatchStateTracker::OnPermissionAdded(
102    MediaGalleriesPreferences* preferences,
103    const std::string& extension_id,
104    MediaGalleryPrefId gallery_id) {
105  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
106  // Granted gallery permission.
107  if (HasGalleryWatchInfo(extension_id, gallery_id, false))
108    SetupGalleryWatch(extension_id, gallery_id, preferences);
109}
110
111void GalleryWatchStateTracker::OnPermissionRemoved(
112    MediaGalleriesPreferences* preferences,
113    const std::string& extension_id,
114    MediaGalleryPrefId gallery_id) {
115  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
116  // Revoked gallery permission.
117  if (HasGalleryWatchInfo(extension_id, gallery_id, true))
118    RemoveGalleryWatch(extension_id, gallery_id, preferences);
119}
120
121void GalleryWatchStateTracker::OnGalleryRemoved(MediaGalleriesPreferences* pref,
122                                                MediaGalleryPrefId gallery_id) {
123  for (WatchedExtensionsMap::const_iterator it =
124           watched_extensions_map_.begin();
125       it != watched_extensions_map_.end();
126       ++it) {
127    if (it->second.find(gallery_id) != it->second.end())
128      RemoveGalleryWatch(it->first, gallery_id, pref);
129  }
130}
131
132MediaGalleryPrefIdSet
133GalleryWatchStateTracker::GetAllWatchedGalleryIDsForExtension(
134    const std::string& extension_id) const {
135  MediaGalleryPrefIdSet gallery_ids;
136  WatchedExtensionsMap::const_iterator extension_id_iter =
137      watched_extensions_map_.find(extension_id);
138  if (extension_id_iter != watched_extensions_map_.end()) {
139    for (WatchedGalleriesMap::const_iterator gallery_id_iter =
140             extension_id_iter->second.begin();
141         gallery_id_iter != extension_id_iter->second.end();
142         ++gallery_id_iter) {
143      gallery_ids.insert(gallery_id_iter->first);
144    }
145  }
146  return gallery_ids;
147}
148
149void GalleryWatchStateTracker::RemoveAllGalleryWatchersForExtension(
150    const std::string& extension_id,
151    MediaGalleriesPreferences* preferences) {
152  WatchedExtensionsMap::iterator extension_id_iter =
153      watched_extensions_map_.find(extension_id);
154  if (extension_id_iter == watched_extensions_map_.end())
155    return;
156  const WatchedGalleriesMap& galleries = extension_id_iter->second;
157  for (WatchedGalleriesMap::const_iterator gallery_id_iter = galleries.begin();
158       gallery_id_iter != galleries.end(); ++gallery_id_iter)
159    RemoveGalleryWatch(extension_id, gallery_id_iter->second, preferences);
160  watched_extensions_map_.erase(extension_id_iter);
161  WriteToStorage(extension_id);
162}
163
164void GalleryWatchStateTracker::OnGalleryWatchAdded(
165    const std::string& extension_id,
166    MediaGalleryPrefId gallery_id) {
167  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
168  bool update_storage =
169      AddWatchedGalleryIdInfoForExtension(extension_id, gallery_id);
170  if (update_storage)
171    WriteToStorage(extension_id);
172}
173
174void GalleryWatchStateTracker::OnGalleryWatchRemoved(
175    const std::string& extension_id,
176    MediaGalleryPrefId gallery_id) {
177  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
178  if (!ContainsKey(watched_extensions_map_, extension_id))
179    return;
180  watched_extensions_map_[extension_id].erase(gallery_id);
181  if (watched_extensions_map_[extension_id].empty())
182    watched_extensions_map_.erase(extension_id);
183  WriteToStorage(extension_id);
184}
185
186void GalleryWatchStateTracker::OnExtensionLoaded(
187    content::BrowserContext* browser_context,
188    const Extension* extension) {
189  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
190  StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
191  if (!storage)
192    return;
193  storage->GetExtensionValue(
194      extension->id(),
195      kRegisteredGalleryWatchers,
196      base::Bind(&GalleryWatchStateTracker::ReadFromStorage,
197                 AsWeakPtr(),
198                 extension->id()));
199}
200
201void GalleryWatchStateTracker::OnExtensionUnloaded(
202    content::BrowserContext* browser_context,
203    const Extension* extension,
204    UnloadedExtensionInfo::Reason reason) {
205  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
206  if (!ContainsKey(watched_extensions_map_, extension->id()))
207    return;
208  content::BrowserThread::PostTask(
209      content::BrowserThread::FILE, FROM_HERE,
210      base::Bind(&GalleryWatchManager::OnExtensionUnloaded,
211                 profile_,
212                 extension->id()));
213  for (WatchedGalleriesMap::iterator iter =
214       watched_extensions_map_[extension->id()].begin();
215       iter != watched_extensions_map_[extension->id()].end(); ++iter) {
216    iter->second = false;
217  }
218}
219
220void GalleryWatchStateTracker::WriteToStorage(const std::string& extension_id) {
221  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
222  StateStore* storage = ExtensionSystem::Get(profile_)->state_store();
223  if (!storage)
224    return;
225  MediaGalleryPrefIdSet gallery_ids =
226      GetAllWatchedGalleryIDsForExtension(extension_id);
227  storage->SetExtensionValue(
228      extension_id,
229      kRegisteredGalleryWatchers,
230      WatchedGalleryIdsToValue(gallery_ids).PassAs<base::Value>());
231}
232
233void GalleryWatchStateTracker::ReadFromStorage(
234    const std::string& extension_id,
235    scoped_ptr<base::Value> value) {
236  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
237  MediaGalleriesPreferences* preferences =
238      g_browser_process->media_file_system_registry()->GetPreferences(profile_);
239  base::ListValue* list = NULL;
240  if (!value.get() || !value->GetAsList(&list))
241    return;
242  MediaGalleryPrefIdSet gallery_ids = WatchedGalleryIdsFromValue(list);
243  if (gallery_ids.empty())
244    return;
245
246  for (MediaGalleryPrefIdSet::const_iterator id_iter = gallery_ids.begin();
247       id_iter != gallery_ids.end(); ++id_iter) {
248    watched_extensions_map_[extension_id][*id_iter] = false;
249    SetupGalleryWatch(extension_id, *id_iter, preferences);
250  }
251}
252
253void GalleryWatchStateTracker::SetupGalleryWatch(
254    const std::string& extension_id,
255    MediaGalleryPrefId gallery_id,
256    MediaGalleriesPreferences* preferences) {
257  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
258  const Extension* extension = GetExtensionById(profile_, extension_id);
259  DCHECK(extension);
260  base::FilePath gallery_file_path(preferences->LookUpGalleryPathForExtension(
261      gallery_id, extension, false));
262  if (gallery_file_path.empty())
263    return;
264  MediaGalleriesPrivateEventRouter* router =
265      MediaGalleriesPrivateAPI::Get(profile_)->GetEventRouter();
266  DCHECK(router);
267  content::BrowserThread::PostTaskAndReplyWithResult(
268      content::BrowserThread::FILE,
269      FROM_HERE,
270      base::Bind(&GalleryWatchManager::SetupGalleryWatch,
271                 profile_,
272                 gallery_id,
273                 gallery_file_path,
274                 extension_id,
275                 router->AsWeakPtr()),
276      base::Bind(&GalleryWatchStateTracker::HandleSetupGalleryWatchResponse,
277                 AsWeakPtr(),
278                 extension_id,
279                 gallery_id));
280}
281
282void GalleryWatchStateTracker::RemoveGalleryWatch(
283    const std::string& extension_id,
284    MediaGalleryPrefId gallery_id,
285    MediaGalleriesPreferences* preferences) {
286  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
287  const Extension* extension = GetExtensionById(profile_, extension_id);
288  DCHECK(extension);
289  base::FilePath gallery_file_path(preferences->LookUpGalleryPathForExtension(
290      gallery_id, extension, true));
291  if (gallery_file_path.empty())
292    return;
293  content::BrowserThread::PostTask(
294      content::BrowserThread::FILE, FROM_HERE,
295      base::Bind(&GalleryWatchManager::RemoveGalleryWatch,
296                 profile_,
297                 gallery_file_path,
298                 extension_id));
299  watched_extensions_map_[extension_id][gallery_id] = false;
300}
301
302bool GalleryWatchStateTracker::HasGalleryWatchInfo(
303    const std::string& extension_id,
304    MediaGalleryPrefId gallery_id,
305    bool has_active_watcher) {
306  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
307  return (ContainsKey(watched_extensions_map_, extension_id) &&
308          ContainsKey(watched_extensions_map_[extension_id], gallery_id) &&
309          watched_extensions_map_[extension_id][gallery_id] ==
310              has_active_watcher);
311}
312
313void GalleryWatchStateTracker::HandleSetupGalleryWatchResponse(
314    const std::string& extension_id,
315    MediaGalleryPrefId gallery_id,
316    bool success) {
317  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
318  if (!success)
319    return;  // Failed to setup the gallery watch for the given extension.
320  AddWatchedGalleryIdInfoForExtension(extension_id, gallery_id);
321}
322
323bool GalleryWatchStateTracker::AddWatchedGalleryIdInfoForExtension(
324    const std::string& extension_id,
325    MediaGalleryPrefId gallery_id) {
326  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
327  if (HasGalleryWatchInfo(extension_id, gallery_id, true))
328    return false;
329  watched_extensions_map_[extension_id][gallery_id] = true;
330  return true;
331}
332
333}  // namespace extensions
334