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