blob: e257dcea160ba78f8ecb5a642edf648774f82e58 [file] [log] [blame]
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001// Copyright 2013 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/chromeos/extensions/file_manager/private_api_tasks.h"
6
7#include "chrome/browser/chromeos/drive/drive_app_registry.h"
8#include "chrome/browser/chromeos/drive/drive_integration_service.h"
9#include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h"
10#include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h"
11#include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
12#include "chrome/browser/chromeos/fileapi/file_system_backend.h"
13#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
14#include "chrome/browser/extensions/extension_service.h"
15#include "chrome/browser/extensions/extension_system.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/ui/browser_finder.h"
18#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
19#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
20#include "content/public/browser/browser_context.h"
21#include "content/public/browser/render_view_host.h"
22#include "content/public/browser/storage_partition.h"
23#include "webkit/browser/fileapi/file_system_context.h"
24#include "webkit/browser/fileapi/file_system_url.h"
25
26using content::BrowserContext;
27using extensions::app_file_handler_util::FindFileHandlersForFiles;
28using extensions::app_file_handler_util::PathAndMimeTypeSet;
29using extensions::Extension;
30using fileapi::FileSystemURL;
31
32namespace file_manager {
33namespace {
34
35// Error messages.
36const char kInvalidFileUrl[] = "Invalid file URL";
37
38// Default icon path for drive docs.
39const char kDefaultIcon[] = "images/filetype_generic.png";
40
41// Logs the default task for debugging.
42void LogDefaultTask(const std::set<std::string>& mime_types,
43 const std::set<std::string>& suffixes,
44 const std::string& task_id) {
45 if (!mime_types.empty()) {
46 std::string mime_types_str;
47 for (std::set<std::string>::const_iterator iter = mime_types.begin();
48 iter != mime_types.end(); ++iter) {
49 if (iter == mime_types.begin()) {
50 mime_types_str = *iter;
51 } else {
52 mime_types_str += ", " + *iter;
53 }
54 }
55 VLOG(1) << "Associating task " << task_id
56 << " with the following MIME types: ";
57 VLOG(1) << " " << mime_types_str;
58 }
59
60 if (!suffixes.empty()) {
61 std::string suffixes_str;
62 for (std::set<std::string>::const_iterator iter = suffixes.begin();
63 iter != suffixes.end(); ++iter) {
64 if (iter == suffixes.begin()) {
65 suffixes_str = *iter;
66 } else {
67 suffixes_str += ", " + *iter;
68 }
69 }
70 VLOG(1) << "Associating task " << task_id
71 << " with the following suffixes: ";
72 VLOG(1) << " " << suffixes_str;
73 }
74}
75
76// Returns a task id for the web app with |app_id|.
77std::string MakeWebAppTaskId(const std::string& app_id) {
78 // TODO(gspencer): For now, the action id is always "open-with", but we
79 // could add any actions that the drive app supports.
80 return file_tasks::MakeTaskID(
81 app_id, file_tasks::kTaskDrive, "open-with");
82}
83
84// Gets the mime types for the given file paths.
85void GetMimeTypesForFileURLs(const std::vector<base::FilePath>& file_paths,
86 PathAndMimeTypeSet* files) {
87 for (std::vector<base::FilePath>::const_iterator iter = file_paths.begin();
88 iter != file_paths.end(); ++iter) {
89 files->insert(
90 std::make_pair(*iter, util::GetMimeTypeForPath(*iter)));
91 }
92}
93
94// Make a set of unique filename suffixes out of the list of file URLs.
95std::set<std::string> GetUniqueSuffixes(base::ListValue* file_url_list,
96 fileapi::FileSystemContext* context) {
97 std::set<std::string> suffixes;
98 for (size_t i = 0; i < file_url_list->GetSize(); ++i) {
99 std::string url_str;
100 if (!file_url_list->GetString(i, &url_str))
101 return std::set<std::string>();
102 FileSystemURL url = context->CrackURL(GURL(url_str));
103 if (!url.is_valid() || url.path().empty())
104 return std::set<std::string>();
105 // We'll skip empty suffixes.
106 if (!url.path().Extension().empty())
107 suffixes.insert(url.path().Extension());
108 }
109 return suffixes;
110}
111
112// Make a set of unique MIME types out of the list of MIME types.
113std::set<std::string> GetUniqueMimeTypes(base::ListValue* mime_type_list) {
114 std::set<std::string> mime_types;
115 for (size_t i = 0; i < mime_type_list->GetSize(); ++i) {
116 std::string mime_type;
117 if (!mime_type_list->GetString(i, &mime_type))
118 return std::set<std::string>();
119 // We'll skip empty MIME types.
120 if (!mime_type.empty())
121 mime_types.insert(mime_type);
122 }
123 return mime_types;
124}
125
126} // namespace
127
128ExecuteTaskFunction::ExecuteTaskFunction() {
129}
130
131ExecuteTaskFunction::~ExecuteTaskFunction() {
132}
133
134bool ExecuteTaskFunction::RunImpl() {
135 // First param is task id that was to the extension with getFileTasks call.
136 std::string task_id;
137 if (!args_->GetString(0, &task_id) || !task_id.size())
138 return false;
139
140 // TODO(kaznacheev): Crack the task_id here, store it in the Executor
141 // and avoid passing it around.
142
143 // The second param is the list of files that need to be executed with this
144 // task.
145 ListValue* files_list = NULL;
146 if (!args_->GetList(1, &files_list))
147 return false;
148
149 std::string extension_id;
150 std::string task_type;
151 std::string action_id;
152 if (!file_tasks::CrackTaskID(
153 task_id, &extension_id, &task_type, &action_id)) {
154 LOG(WARNING) << "Invalid task " << task_id;
155 return false;
156 }
157
158 if (!files_list->GetSize())
159 return true;
160
161 content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
162 scoped_refptr<fileapi::FileSystemContext> file_system_context =
163 BrowserContext::GetStoragePartition(profile(), site_instance)->
164 GetFileSystemContext();
165
166 std::vector<FileSystemURL> file_urls;
167 for (size_t i = 0; i < files_list->GetSize(); i++) {
168 std::string file_url_str;
169 if (!files_list->GetString(i, &file_url_str)) {
170 error_ = kInvalidFileUrl;
171 return false;
172 }
173 FileSystemURL url = file_system_context->CrackURL(GURL(file_url_str));
174 if (!chromeos::FileSystemBackend::CanHandleURL(url)) {
175 error_ = kInvalidFileUrl;
176 return false;
177 }
178 file_urls.push_back(url);
179 }
180
181 int32 tab_id = util::GetTabId(dispatcher());
182 return file_tasks::ExecuteFileTask(
183 profile(),
184 source_url(),
185 extension_->id(),
186 tab_id,
187 extension_id,
188 task_type,
189 action_id,
190 file_urls,
191 base::Bind(&ExecuteTaskFunction::OnTaskExecuted, this));
192}
193
194void ExecuteTaskFunction::OnTaskExecuted(bool success) {
195 SetResult(new base::FundamentalValue(success));
196 SendResponse(true);
197}
198
199struct GetFileTasksFunction::FileInfo {
200 GURL file_url;
201 base::FilePath file_path;
202 std::string mime_type;
203};
204
205struct GetFileTasksFunction::TaskInfo {
206 TaskInfo(const string16& app_name, const GURL& icon_url)
207 : app_name(app_name), icon_url(icon_url) {
208 }
209
210 string16 app_name;
211 GURL icon_url;
212};
213
214GetFileTasksFunction::GetFileTasksFunction() {
215}
216
217GetFileTasksFunction::~GetFileTasksFunction() {
218}
219
220// static
221void GetFileTasksFunction::GetAvailableDriveTasks(
222 drive::DriveAppRegistry* registry,
223 const FileInfoList& file_info_list,
224 TaskInfoMap* task_info_map) {
225 DCHECK(registry);
226 DCHECK(task_info_map);
227 DCHECK(task_info_map->empty());
228
229 bool is_first = true;
230 for (size_t i = 0; i < file_info_list.size(); ++i) {
231 const FileInfo& file_info = file_info_list[i];
232 if (file_info.file_path.empty())
233 continue;
234
235 ScopedVector<drive::DriveAppInfo> app_info_list;
236 registry->GetAppsForFile(
237 file_info.file_path, file_info.mime_type, &app_info_list);
238
239 if (is_first) {
240 // For the first file, we store all the info.
241 for (size_t j = 0; j < app_info_list.size(); ++j) {
242 const drive::DriveAppInfo& app_info = *app_info_list[j];
243 GURL icon_url = util::FindPreferredIcon(app_info.app_icons,
244 util::kPreferredIconSize);
245 task_info_map->insert(std::pair<std::string, TaskInfo>(
246 MakeWebAppTaskId(app_info.app_id),
247 TaskInfo(app_info.app_name, icon_url)));
248 }
249 } else {
250 // For remaining files, take the intersection with the current result,
251 // based on the task id.
252 std::set<std::string> task_id_set;
253 for (size_t j = 0; j < app_info_list.size(); ++j) {
254 task_id_set.insert(MakeWebAppTaskId(app_info_list[j]->app_id));
255 }
256 for (TaskInfoMap::iterator iter = task_info_map->begin();
257 iter != task_info_map->end(); ) {
258 if (task_id_set.find(iter->first) == task_id_set.end()) {
259 task_info_map->erase(iter++);
260 } else {
261 ++iter;
262 }
263 }
264 }
265
266 is_first = false;
267 }
268}
269
270void GetFileTasksFunction::FindDefaultDriveTasks(
271 const FileInfoList& file_info_list,
272 const TaskInfoMap& task_info_map,
273 std::set<std::string>* default_tasks) {
274 DCHECK(default_tasks);
275
276 for (size_t i = 0; i < file_info_list.size(); ++i) {
277 const FileInfo& file_info = file_info_list[i];
278 std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
279 profile_, file_info.mime_type, file_info.file_path.Extension());
280 if (task_info_map.find(task_id) != task_info_map.end())
281 default_tasks->insert(task_id);
282 }
283}
284
285// static
286void GetFileTasksFunction::CreateDriveTasks(
287 const TaskInfoMap& task_info_map,
288 const std::set<std::string>& default_tasks,
289 ListValue* result_list,
290 bool* default_already_set) {
291 DCHECK(result_list);
292 DCHECK(default_already_set);
293
294 for (TaskInfoMap::const_iterator iter = task_info_map.begin();
295 iter != task_info_map.end(); ++iter) {
296 DictionaryValue* task = new DictionaryValue;
297 task->SetString("taskId", iter->first);
298 task->SetString("title", iter->second.app_name);
299
300 const GURL& icon_url = iter->second.icon_url;
301 if (!icon_url.is_empty())
302 task->SetString("iconUrl", icon_url.spec());
303
304 task->SetBoolean("driveApp", true);
305
306 // Once we set a default app, we don't want to set any more.
307 if (!(*default_already_set) &&
308 default_tasks.find(iter->first) != default_tasks.end()) {
309 task->SetBoolean("isDefault", true);
310 *default_already_set = true;
311 } else {
312 task->SetBoolean("isDefault", false);
313 }
314 result_list->Append(task);
315 }
316}
317
318// Find special tasks here for Drive (Blox) apps. Iterate through matching drive
319// apps and add them, with generated task ids. Extension ids will be the app_ids
320// from drive. We'll know that they are drive apps because the extension id will
321// begin with kDriveTaskExtensionPrefix.
322bool GetFileTasksFunction::FindDriveAppTasks(
323 const FileInfoList& file_info_list,
324 ListValue* result_list,
325 bool* default_already_set) {
326 DCHECK(result_list);
327 DCHECK(default_already_set);
328
329 if (file_info_list.empty())
330 return true;
331
332 drive::DriveIntegrationService* integration_service =
333 drive::DriveIntegrationServiceFactory::GetForProfile(profile_);
334 // |integration_service| is NULL if Drive is disabled. We return true in this
335 // case because there might be other extension tasks, even if we don't have
336 // any to add.
337 if (!integration_service || !integration_service->drive_app_registry())
338 return true;
339
340 drive::DriveAppRegistry* registry =
341 integration_service->drive_app_registry();
342 DCHECK(registry);
343
344 // Map of task_id to TaskInfo of available tasks.
345 TaskInfoMap task_info_map;
346 GetAvailableDriveTasks(registry, file_info_list, &task_info_map);
347 std::set<std::string> default_tasks;
348 FindDefaultDriveTasks(file_info_list, task_info_map, &default_tasks);
349 CreateDriveTasks(
350 task_info_map, default_tasks, result_list, default_already_set);
351 return true;
352}
353
354bool GetFileTasksFunction::FindAppTasks(
355 const std::vector<base::FilePath>& file_paths,
356 ListValue* result_list,
357 bool* default_already_set) {
358 DCHECK(!file_paths.empty());
359 ExtensionService* service = profile_->GetExtensionService();
360 if (!service)
361 return false;
362
363 PathAndMimeTypeSet files;
364 GetMimeTypesForFileURLs(file_paths, &files);
365 std::set<std::string> default_tasks;
366 for (PathAndMimeTypeSet::iterator it = files.begin(); it != files.end();
367 ++it) {
368 default_tasks.insert(file_tasks::GetDefaultTaskIdFromPrefs(
369 profile_, it->second, it->first.Extension()));
370 }
371
372 for (ExtensionSet::const_iterator iter = service->extensions()->begin();
373 iter != service->extensions()->end();
374 ++iter) {
375 const Extension* extension = iter->get();
376
377 // We don't support using hosted apps to open files.
378 if (!extension->is_platform_app())
379 continue;
380
381 if (profile_->IsOffTheRecord() &&
382 !service->IsIncognitoEnabled(extension->id()))
383 continue;
384
385 typedef std::vector<const extensions::FileHandlerInfo*> FileHandlerList;
386 FileHandlerList file_handlers = FindFileHandlersForFiles(*extension, files);
387 if (file_handlers.empty())
388 continue;
389
390 for (FileHandlerList::iterator i = file_handlers.begin();
391 i != file_handlers.end(); ++i) {
392 DictionaryValue* task = new DictionaryValue;
393 std::string task_id = file_tasks::MakeTaskID(
394 extension->id(), file_tasks::kTaskApp, (*i)->id);
395 task->SetString("taskId", task_id);
396 task->SetString("title", (*i)->title);
397 if (!(*default_already_set) && ContainsKey(default_tasks, task_id)) {
398 task->SetBoolean("isDefault", true);
399 *default_already_set = true;
400 } else {
401 task->SetBoolean("isDefault", false);
402 }
403
404 GURL best_icon = extensions::ExtensionIconSource::GetIconURL(
405 extension,
406 util::kPreferredIconSize,
407 ExtensionIconSet::MATCH_BIGGER,
408 false, // grayscale
409 NULL); // exists
410 if (!best_icon.is_empty())
411 task->SetString("iconUrl", best_icon.spec());
412 else
413 task->SetString("iconUrl", kDefaultIcon);
414
415 task->SetBoolean("driveApp", false);
416 result_list->Append(task);
417 }
418 }
419
420 return true;
421}
422
423bool GetFileTasksFunction::RunImpl() {
424 // First argument is the list of files to get tasks for.
425 ListValue* files_list = NULL;
426 if (!args_->GetList(0, &files_list))
427 return false;
428
429 if (files_list->GetSize() == 0)
430 return false;
431
432 // Second argument is the list of mime types of each of the files in the list.
433 ListValue* mime_types_list = NULL;
434 if (!args_->GetList(1, &mime_types_list))
435 return false;
436
437 // MIME types can either be empty, or there needs to be one for each file.
438 if (mime_types_list->GetSize() != files_list->GetSize() &&
439 mime_types_list->GetSize() != 0)
440 return false;
441
442 content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
443 scoped_refptr<fileapi::FileSystemContext> file_system_context =
444 BrowserContext::GetStoragePartition(profile(), site_instance)->
445 GetFileSystemContext();
446
447 // Collect all the URLs, convert them to GURLs, and crack all the urls into
448 // file paths.
449 FileInfoList info_list;
450 std::vector<GURL> file_urls;
451 std::vector<base::FilePath> file_paths;
452 bool has_google_document = false;
453 for (size_t i = 0; i < files_list->GetSize(); ++i) {
454 FileInfo info;
455 std::string file_url_str;
456 if (!files_list->GetString(i, &file_url_str))
457 return false;
458
459 if (mime_types_list->GetSize() != 0 &&
460 !mime_types_list->GetString(i, &info.mime_type))
461 return false;
462
463 GURL file_url(file_url_str);
464 fileapi::FileSystemURL file_system_url(
465 file_system_context->CrackURL(file_url));
466 if (!chromeos::FileSystemBackend::CanHandleURL(file_system_url))
467 continue;
468
469 file_urls.push_back(file_url);
470 file_paths.push_back(file_system_url.path());
471
472 info.file_url = file_url;
473 info.file_path = file_system_url.path();
474 info_list.push_back(info);
475
476 if (google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
477 info.file_path) &
478 google_apis::ResourceEntry::KIND_OF_GOOGLE_DOCUMENT) {
479 has_google_document = true;
480 }
481 }
482
483 ListValue* result_list = new ListValue();
484 SetResult(result_list);
485
486 // Find the Drive apps first, because we want them to take precedence
487 // when setting the default app.
488 bool default_already_set = false;
489 // Google document are not opened by drive apps but file manager.
490 if (!has_google_document) {
491 if (!FindDriveAppTasks(info_list, result_list, &default_already_set))
492 return false;
493 }
494
495 // Take the union of platform app file handlers, and all previous Drive
496 // and extension tasks. As above, we know there aren't duplicates because
497 // they're entirely different kinds of
498 // tasks.
499 if (!FindAppTasks(file_paths, result_list, &default_already_set))
500 return false;
501
502 // Take the union of Drive and extension tasks: Because any Drive tasks we
503 // found must apply to all of the files (intersection), and because the same
504 // is true of the extensions, we simply take the union of two lists by adding
505 // the extension tasks to the Drive task list. We know there aren't duplicates
506 // because they're entirely different kinds of tasks, but there could be both
507 // kinds of tasks for a file type (an image file, for instance).
508 file_tasks::FileBrowserHandlerList common_tasks;
509 file_tasks::FileBrowserHandlerList default_tasks;
510 if (!file_tasks::FindCommonTasks(profile_, file_urls, &common_tasks))
511 return false;
512 file_tasks::FindDefaultTasks(profile_, file_paths,
513 common_tasks, &default_tasks);
514
515 ExtensionService* service =
516 extensions::ExtensionSystem::Get(profile_)->extension_service();
517 for (file_tasks::FileBrowserHandlerList::const_iterator iter =
518 common_tasks.begin();
519 iter != common_tasks.end();
520 ++iter) {
521 const FileBrowserHandler* handler = *iter;
522 const std::string extension_id = handler->extension_id();
523 const Extension* extension = service->GetExtensionById(extension_id, false);
524 CHECK(extension);
525 DictionaryValue* task = new DictionaryValue;
526 task->SetString("taskId", file_tasks::MakeTaskID(
527 extension_id, file_tasks::kTaskFile, handler->id()));
528 task->SetString("title", handler->title());
529 // TODO(zelidrag): Figure out how to expose icon URL that task defined in
530 // manifest instead of the default extension icon.
531 GURL icon = extensions::ExtensionIconSource::GetIconURL(
532 extension,
533 extension_misc::EXTENSION_ICON_BITTY,
534 ExtensionIconSet::MATCH_BIGGER,
535 false, // grayscale
536 NULL); // exists
537 task->SetString("iconUrl", icon.spec());
538 task->SetBoolean("driveApp", false);
539
540 // Only set the default if there isn't already a default set.
541 if (!default_already_set &&
542 std::find(default_tasks.begin(), default_tasks.end(), *iter) !=
543 default_tasks.end()) {
544 task->SetBoolean("isDefault", true);
545 default_already_set = true;
546 } else {
547 task->SetBoolean("isDefault", false);
548 }
549
550 result_list->Append(task);
551 }
552
553 SendResponse(true);
554 return true;
555}
556
557SetDefaultTaskFunction::SetDefaultTaskFunction() {
558}
559
560SetDefaultTaskFunction::~SetDefaultTaskFunction() {
561}
562
563bool SetDefaultTaskFunction::RunImpl() {
564 // First param is task id that was to the extension with setDefaultTask call.
565 std::string task_id;
566 if (!args_->GetString(0, &task_id) || !task_id.size())
567 return false;
568
569 base::ListValue* file_url_list;
570 if (!args_->GetList(1, &file_url_list))
571 return false;
572
573 content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
574 scoped_refptr<fileapi::FileSystemContext> context =
575 BrowserContext::GetStoragePartition(profile(), site_instance)->
576 GetFileSystemContext();
577
578 std::set<std::string> suffixes =
579 GetUniqueSuffixes(file_url_list, context.get());
580
581 // MIME types are an optional parameter.
582 base::ListValue* mime_type_list;
583 std::set<std::string> mime_types;
584 if (args_->GetList(2, &mime_type_list) && !mime_type_list->empty()) {
585 if (mime_type_list->GetSize() != file_url_list->GetSize())
586 return false;
587 mime_types = GetUniqueMimeTypes(mime_type_list);
588 }
589
590 if (VLOG_IS_ON(1))
591 LogDefaultTask(mime_types, suffixes, task_id);
592
593 // If there weren't any mime_types, and all the suffixes were blank,
594 // then we "succeed", but don't actually associate with anything.
595 // Otherwise, any time we set the default on a file with no extension
596 // on the local drive, we'd fail.
597 // TODO(gspencer): Fix file manager so that it never tries to set default in
598 // cases where extensionless local files are part of the selection.
599 if (suffixes.empty() && mime_types.empty()) {
600 SetResult(new base::FundamentalValue(true));
601 return true;
602 }
603
604 file_tasks::UpdateDefaultTask(profile_, task_id, suffixes, mime_types);
605
606 return true;
607}
608
609ViewFilesFunction::ViewFilesFunction() {
610}
611
612ViewFilesFunction::~ViewFilesFunction() {
613}
614
615bool ViewFilesFunction::RunImpl() {
616 if (args_->GetSize() < 1) {
617 return false;
618 }
619
620 ListValue* path_list = NULL;
621 args_->GetList(0, &path_list);
622 DCHECK(path_list);
623
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100624 std::vector<base::FilePath> files;
625 for (size_t i = 0; i < path_list->GetSize(); ++i) {
626 std::string url_as_string;
627 path_list->GetString(i, &url_as_string);
628 base::FilePath path = util::GetLocalPathFromURL(
629 render_view_host(), profile(), GURL(url_as_string));
630 if (path.empty())
631 return false;
632 files.push_back(path);
633 }
634
635 Browser* browser = chrome::FindOrCreateTabbedBrowser(
636 profile_, chrome::HOST_DESKTOP_TYPE_ASH);
637 bool success = browser;
638
639 if (browser) {
640 for (size_t i = 0; i < files.size(); ++i) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100641 bool handled = util::ExecuteBuiltinHandler(browser, files[i]);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100642 if (!handled && files.size() == 1)
643 success = false;
644 }
645 }
646
647 SetResult(Value::CreateBooleanValue(success));
648 SendResponse(true);
649 return true;
650}
651
652} // namespace file_manager