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