1// Copyright 2014 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/drive/drive_app_registry.h"
6
7#include <algorithm>
8#include <set>
9#include <utility>
10
11#include "base/callback.h"
12#include "base/files/file_path.h"
13#include "chrome/browser/drive/drive_app_registry_observer.h"
14#include "chrome/browser/drive/drive_service_interface.h"
15#include "content/public/browser/browser_thread.h"
16#include "google_apis/drive/drive_api_parser.h"
17#include "google_apis/google_api_keys.h"
18
19using content::BrowserThread;
20
21namespace {
22
23// Add {selector -> app_id} mapping to |map|.
24void AddAppSelectorList(const ScopedVector<std::string>& selectors,
25                        const std::string& app_id,
26                        std::multimap<std::string, std::string>* map) {
27  for (size_t i = 0; i < selectors.size(); ++i)
28    map->insert(std::make_pair(*selectors[i], app_id));
29}
30
31// Append list of app ids in |map| looked up by |selector| to |matched_apps|.
32void FindAppsForSelector(const std::string& selector,
33                         const std::multimap<std::string, std::string>& map,
34                         std::vector<std::string>* matched_apps) {
35  typedef std::multimap<std::string, std::string>::const_iterator iterator;
36  std::pair<iterator, iterator> range = map.equal_range(selector);
37  for (iterator it = range.first; it != range.second; ++it)
38    matched_apps->push_back(it->second);
39}
40
41void RemoveAppFromSelector(const std::string& app_id,
42                           std::multimap<std::string, std::string>* map) {
43  typedef std::multimap<std::string, std::string>::iterator iterator;
44  for (iterator it = map->begin(); it != map->end(); ) {
45    iterator now = it++;
46    if (now->second == app_id)
47      map->erase(now);
48  }
49}
50
51}  // namespace
52
53namespace drive {
54
55DriveAppInfo::DriveAppInfo() {
56}
57
58DriveAppInfo::DriveAppInfo(
59    const std::string& app_id,
60    const std::string& product_id,
61    const IconList& app_icons,
62    const IconList& document_icons,
63    const std::string& app_name,
64    const GURL& create_url,
65    bool is_removable)
66    : app_id(app_id),
67      product_id(product_id),
68      app_icons(app_icons),
69      document_icons(document_icons),
70      app_name(app_name),
71      create_url(create_url),
72      is_removable(is_removable) {
73}
74
75DriveAppInfo::~DriveAppInfo() {
76}
77
78DriveAppRegistry::DriveAppRegistry(DriveServiceInterface* drive_service)
79    : drive_service_(drive_service),
80      is_updating_(false),
81      weak_ptr_factory_(this) {
82}
83
84DriveAppRegistry::~DriveAppRegistry() {
85}
86
87void DriveAppRegistry::GetAppsForFile(
88    const base::FilePath::StringType& file_extension,
89    const std::string& mime_type,
90    std::vector<DriveAppInfo>* apps) const {
91  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
92
93  std::vector<std::string> matched_apps;
94  if (!file_extension.empty()) {
95    const std::string without_dot =
96        base::FilePath(file_extension.substr(1)).AsUTF8Unsafe();
97    FindAppsForSelector(without_dot, extension_map_, &matched_apps);
98  }
99  if (!mime_type.empty())
100    FindAppsForSelector(mime_type, mimetype_map_, &matched_apps);
101
102  // Insert found Drive apps into |apps|, but skip duplicate results.
103  std::set<std::string> inserted_app_ids;
104  for (size_t i = 0; i < matched_apps.size(); ++i) {
105    if (inserted_app_ids.count(matched_apps[i]) == 0) {
106      inserted_app_ids.insert(matched_apps[i]);
107      std::map<std::string, DriveAppInfo>::const_iterator it =
108          all_apps_.find(matched_apps[i]);
109      DCHECK(it != all_apps_.end());
110      apps->push_back(it->second);
111    }
112  }
113}
114
115void DriveAppRegistry::GetAppList(std::vector<DriveAppInfo>* apps) const {
116  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
117
118  apps->clear();
119  for (std::map<std::string, DriveAppInfo>::const_iterator
120          it = all_apps_.begin(); it != all_apps_.end(); ++it) {
121    apps->push_back(it->second);
122  }
123}
124
125void DriveAppRegistry::Update() {
126  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127
128  if (is_updating_)  // There is already an update in progress.
129    return;
130  is_updating_ = true;
131
132  drive_service_->GetAppList(
133      base::Bind(&DriveAppRegistry::UpdateAfterGetAppList,
134                 weak_ptr_factory_.GetWeakPtr()));
135}
136
137void DriveAppRegistry::UpdateAfterGetAppList(
138    google_apis::GDataErrorCode gdata_error,
139    scoped_ptr<google_apis::AppList> app_list) {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
141
142  DCHECK(is_updating_);
143  is_updating_ = false;
144
145  // Failed to fetch the data from the server. We can do nothing here.
146  if (gdata_error != google_apis::HTTP_SUCCESS)
147    return;
148
149  DCHECK(app_list);
150  UpdateFromAppList(*app_list);
151}
152
153void DriveAppRegistry::UpdateFromAppList(const google_apis::AppList& app_list) {
154  all_apps_.clear();
155  extension_map_.clear();
156  mimetype_map_.clear();
157
158  for (size_t i = 0; i < app_list.items().size(); ++i) {
159    const google_apis::AppResource& app = *app_list.items()[i];
160    const std::string id = app.application_id();
161
162    DriveAppInfo::IconList app_icons;
163    DriveAppInfo::IconList document_icons;
164    for (size_t j = 0; j < app.icons().size(); ++j) {
165      const google_apis::DriveAppIcon& icon = *app.icons()[j];
166      if (icon.icon_url().is_empty())
167        continue;
168      if (icon.category() == google_apis::DriveAppIcon::APPLICATION)
169        app_icons.push_back(std::make_pair(icon.icon_side_length(),
170                                           icon.icon_url()));
171      if (icon.category() == google_apis::DriveAppIcon::DOCUMENT)
172        document_icons.push_back(std::make_pair(icon.icon_side_length(),
173                                                icon.icon_url()));
174    }
175
176    all_apps_[id] = DriveAppInfo(app.application_id(),
177                                 app.product_id(),
178                                 app_icons,
179                                 document_icons,
180                                 app.name(),
181                                 app.create_url(),
182                                 app.is_removable());
183
184    // TODO(kinaba): consider taking primary/secondary distinction into account.
185    AddAppSelectorList(app.primary_mimetypes(), id, &mimetype_map_);
186    AddAppSelectorList(app.secondary_mimetypes(), id, &mimetype_map_);
187    AddAppSelectorList(app.primary_file_extensions(), id, &extension_map_);
188    AddAppSelectorList(app.secondary_file_extensions(), id, &extension_map_);
189  }
190
191  FOR_EACH_OBSERVER(DriveAppRegistryObserver,
192                    observers_,
193                    OnDriveAppRegistryUpdated());
194}
195
196void DriveAppRegistry::AddObserver(DriveAppRegistryObserver* observer) {
197  observers_.AddObserver(observer);
198}
199
200void DriveAppRegistry::RemoveObserver(DriveAppRegistryObserver* observer) {
201  observers_.RemoveObserver(observer);
202}
203
204void DriveAppRegistry::UninstallApp(const std::string& app_id,
205                                    const UninstallCallback& callback) {
206  DCHECK(!callback.is_null());
207
208  drive_service_->UninstallApp(app_id,
209                               base::Bind(&DriveAppRegistry::OnAppUninstalled,
210                                          weak_ptr_factory_.GetWeakPtr(),
211                                          app_id,
212                                          callback));
213}
214
215void DriveAppRegistry::OnAppUninstalled(const std::string& app_id,
216                                        const UninstallCallback& callback,
217                                        google_apis::GDataErrorCode error) {
218  if (error == google_apis::HTTP_NO_CONTENT) {
219    all_apps_.erase(app_id);
220    RemoveAppFromSelector(app_id, &mimetype_map_);
221    RemoveAppFromSelector(app_id, &extension_map_);
222  }
223  callback.Run(error);
224}
225
226// static
227bool DriveAppRegistry::IsAppUninstallSupported() {
228  return google_apis::IsGoogleChromeAPIKeyUsed();
229}
230
231namespace util {
232
233GURL FindPreferredIcon(const DriveAppInfo::IconList& icons,
234                       int preferred_size) {
235  if (icons.empty())
236    return GURL();
237
238  DriveAppInfo::IconList sorted_icons = icons;
239  std::sort(sorted_icons.rbegin(), sorted_icons.rend());
240
241  // Go forward while the size is larger or equal to preferred_size.
242  size_t i = 1;
243  while (i < sorted_icons.size() && sorted_icons[i].first >= preferred_size)
244    ++i;
245  return sorted_icons[i - 1].second;
246}
247
248}  // namespace util
249}  // namespace drive
250