blob: fd72362b6bbfeb3d5059d14dacdcf368988a156f [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00005'use strict';
6
Torne (Richard Coles)58218062012-11-14 11:43:16 +00007/**
8 * This object encapsulates everything related to tasks execution.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00009 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +000010 * @param {FileManager} fileManager FileManager instance.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000011 * @param {Object=} opt_params File manager load parameters.
12 * @constructor
Torne (Richard Coles)58218062012-11-14 11:43:16 +000013 */
14function FileTasks(fileManager, opt_params) {
15 this.fileManager_ = fileManager;
16 this.params_ = opt_params;
17 this.tasks_ = null;
18 this.defaultTask_ = null;
19
20 /**
21 * List of invocations to be called once tasks are available.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000022 *
23 * @private
Ben Murdochca12bfa2013-07-23 11:17:05 +010024 * @type {Array.<Object>}
Torne (Richard Coles)58218062012-11-14 11:43:16 +000025 */
26 this.pendingInvocations_ = [];
27}
28
29/**
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010030 * Location of the FAQ about the file actions.
31 *
32 * @const
33 * @type {string}
34 */
Torne (Richard Coles)58218062012-11-14 11:43:16 +000035FileTasks.NO_ACTION_FOR_FILE_URL = 'http://support.google.com/chromeos/bin/' +
36 'answer.py?answer=1700055&topic=29026&ctx=topic';
37
38/**
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010039 * Base URL of apps list in the Chrome Web Store. This constant is used in
40 * FileTasks.createWebStoreLink().
41 * @const
42 * @type {string}
43 */
44FileTasks.WEB_STORE_HANDLER_BASE_URL =
45 'https://chrome.google.com/webstore/category/collection/file_handlers';
46
47/**
48 * Returns URL of the Chrome Web Store which show apps supporting the given
49 * file-extension and mime-type.
50 *
51 * @param {string} extension Extension of the file.
52 * @param {string} mimeType Mime type of the file.
53 * @return {string} URL
54 */
55FileTasks.createWebStoreLink = function(extension, mimeType) {
56 var url = FileTasks.WEB_STORE_HANDLER_BASE_URL;
57 url += '?_fe=' + extension.toLowerCase().replace(/[^\w]/g, '');
58 if (mimeType)
59 url += '&_fmt=' + mimeType.replace(/[^-\w\/]/g, '');
60 return url;
61};
62
63/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +000064 * Complete the initialization.
65 *
66 * @param {Array.<string>} urls List of file urls.
67 * @param {Array.<string>=} opt_mimeTypes List of MIME types for each
68 * of the files.
69 */
70FileTasks.prototype.init = function(urls, opt_mimeTypes) {
71 this.urls_ = urls;
72 if (urls.length > 0)
73 chrome.fileBrowserPrivate.getFileTasks(urls, opt_mimeTypes || [],
74 this.onTasks_.bind(this));
75};
76
77/**
78 * Returns amount of tasks.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000079 *
80 * @return {number} amount of tasks.
Torne (Richard Coles)58218062012-11-14 11:43:16 +000081 */
82FileTasks.prototype.size = function() {
83 return (this.tasks_ && this.tasks_.length) || 0;
84};
85
86/**
87 * Callback when tasks found.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000088 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +000089 * @param {Array.<Object>} tasks The tasks.
90 * @private
91 */
92FileTasks.prototype.onTasks_ = function(tasks) {
93 this.processTasks_(tasks);
94 for (var index = 0; index < this.pendingInvocations_.length; index++) {
95 var name = this.pendingInvocations_[index][0];
96 var args = this.pendingInvocations_[index][1];
97 this[name].apply(this, args);
98 }
99 this.pendingInvocations_ = [];
100};
101
102/**
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100103 * The list of known extensions to record UMA.
104 * Note: Because the data is recorded by the index, so new item shouldn't be
105 * inserted.
106 *
107 * @const
108 * @type {Array.<string>}
109 * @private
110 */
111FileTasks.knownExtensions_ = [
112 'other', '.3ga', '.3gp', '.aac', '.alac', '.asf', '.avi', '.bmp', '.csv',
113 '.doc', '.docx', '.flac', '.gif', '.jpeg', '.jpg', '.log', '.m3u', '.m3u8',
114 '.m4a', '.m4v', '.mid', '.mkv', '.mov', '.mp3', '.mp4', '.mpg', '.odf',
115 '.odp', '.ods', '.odt', '.oga', '.ogg', '.ogv', '.pdf', '.png', '.ppt',
116 '.pptx', '.ra', '.ram', '.rar', '.rm', '.rtf', '.wav', '.webm', '.webp',
117 '.wma', '.wmv', '.xls', '.xlsx',
118];
119
120/**
121 * Records trial of opening file grouped by extensions.
122 *
123 * @param {Array.<string>} urls The path to be opened.
124 * @private
125 */
126FileTasks.recordViewingFileTypeUMA_ = function(urls) {
127 for (var i = 0; i < urls.length; i++) {
128 var url = urls[i];
129 var extension = FileType.getExtension(url).toLowerCase();
130 if (FileTasks.knownExtensions_.indexOf(extension) < 0) {
131 extension = 'other';
132 }
133 metrics.recordEnum(
134 'ViewingFileType', extension, FileTasks.knownExtensions_);
135 }
136};
137
138/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000139 * Processes internal tasks.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000140 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000141 * @param {Array.<Object>} tasks The tasks.
142 * @private
143 */
144FileTasks.prototype.processTasks_ = function(tasks) {
145 this.tasks_ = [];
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100146 var id = chrome.runtime.id;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000147 var isOnDrive = false;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000148 for (var index = 0; index < this.urls_.length; ++index) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000149 if (FileType.isOnDrive(this.urls_[index])) {
150 isOnDrive = true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000151 break;
152 }
153 }
154
155 for (var i = 0; i < tasks.length; i++) {
156 var task = tasks[i];
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100157 var taskParts = task.taskId.split('|');
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000158
159 // Skip Drive App if the file is not on Drive.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000160 if (!isOnDrive && task.driveApp)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000161 continue;
162
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100163 // Skip internal Files.app's handlers.
164 if (taskParts[0] == id && (taskParts[2] == 'auto-open' ||
165 taskParts[2] == 'select' || taskParts[2] == 'open')) {
166 continue;
167 }
168
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000169 // Tweak images, titles of internal tasks.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000170 if (taskParts[0] == id && taskParts[1] == 'file') {
171 if (taskParts[2] == 'play') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000172 // TODO(serya): This hack needed until task.iconUrl is working
173 // (see GetFileTasksFileBrowserFunction::RunImpl).
174 task.iconType = 'audio';
175 task.title = loadTimeData.getString('ACTION_LISTEN');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000176 } else if (taskParts[2] == 'mount-archive') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000177 task.iconType = 'archive';
178 task.title = loadTimeData.getString('MOUNT_ARCHIVE');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000179 } else if (taskParts[2] == 'gallery') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000180 task.iconType = 'image';
181 task.title = loadTimeData.getString('ACTION_OPEN');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000182 } else if (taskParts[2] == 'watch') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000183 task.iconType = 'video';
184 task.title = loadTimeData.getString('ACTION_WATCH');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000185 } else if (taskParts[2] == 'open-hosted-generic') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000186 if (this.urls_.length > 1)
187 task.iconType = 'generic';
188 else // Use specific icon.
189 task.iconType = FileType.getIcon(this.urls_[0]);
190 task.title = loadTimeData.getString('ACTION_OPEN');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000191 } else if (taskParts[2] == 'open-hosted-gdoc') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000192 task.iconType = 'gdoc';
193 task.title = loadTimeData.getString('ACTION_OPEN_GDOC');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000194 } else if (taskParts[2] == 'open-hosted-gsheet') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000195 task.iconType = 'gsheet';
196 task.title = loadTimeData.getString('ACTION_OPEN_GSHEET');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000197 } else if (taskParts[2] == 'open-hosted-gslides') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000198 task.iconType = 'gslides';
199 task.title = loadTimeData.getString('ACTION_OPEN_GSLIDES');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000200 } else if (taskParts[2] == 'view-swf') {
201 // Do not render this task if disabled.
202 if (!loadTimeData.getBoolean('SWF_VIEW_ENABLED'))
203 continue;
204 task.iconType = 'generic';
205 task.title = loadTimeData.getString('ACTION_VIEW');
206 } else if (taskParts[2] == 'view-pdf') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000207 // Do not render this task if disabled.
208 if (!loadTimeData.getBoolean('PDF_VIEW_ENABLED'))
209 continue;
210 task.iconType = 'pdf';
211 task.title = loadTimeData.getString('ACTION_VIEW');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000212 } else if (taskParts[2] == 'view-in-browser') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000213 task.iconType = 'generic';
214 task.title = loadTimeData.getString('ACTION_VIEW');
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000215 } else if (taskParts[2] == 'install-crx') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000216 task.iconType = 'generic';
217 task.title = loadTimeData.getString('INSTALL_CRX');
218 }
219 }
220
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000221 if (!task.iconType && taskParts[1] == 'web-intent') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000222 task.iconType = 'generic';
223 }
224
225 this.tasks_.push(task);
226 if (this.defaultTask_ == null && task.isDefault) {
227 this.defaultTask_ = task;
228 }
229 }
230 if (!this.defaultTask_ && this.tasks_.length > 0) {
231 // If we haven't picked a default task yet, then just pick the first one.
232 // This is not the preferred way we want to pick this, but better this than
233 // no default at all if the C++ code didn't set one.
234 this.defaultTask_ = this.tasks_[0];
235 }
236};
237
238/**
239 * Executes default task.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000240 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000241 * @private
242 */
243FileTasks.prototype.executeDefault_ = function() {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100244 var urls = this.urls_;
245 FileTasks.recordViewingFileTypeUMA_(urls);
246 this.executeDefaultInternal_(urls);
247};
248
249/**
250 * Executes default task.
251 *
252 * @param {Array.<string>} urls Urls to execute.
253 * @private
254 */
255FileTasks.prototype.executeDefaultInternal_ = function(urls) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000256 if (this.defaultTask_ != null) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100257 this.executeInternal_(this.defaultTask_.taskId, urls);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000258 return;
259 }
260
261 // We don't have tasks, so try to show a file in a browser tab.
262 // We only do that for single selection to avoid confusion.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100263 if (urls.length == 1) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000264 var callback = function(success) {
265 if (!success) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100266 var filename = decodeURIComponent(urls[0]);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000267 if (filename.indexOf('/') != -1)
268 filename = filename.substr(filename.lastIndexOf('/') + 1);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100269 var extension = filename.lastIndexOf('.') != -1 ?
270 filename.substr(filename.lastIndexOf('.') + 1) : '';
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000271
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100272 this.fileManager_.metadataCache_.get(urls, 'drive', function(props) {
273 var mimeType;
274 if (props && props[0] && props[0].contentMimeType)
275 mimeType = props[0].contentMimeType;
276
Ben Murdocheb525c52013-07-10 11:40:50 +0100277 var messageString = extension == 'exe' ? 'NO_ACTION_FOR_EXECUTABLE' :
278 'NO_ACTION_FOR_FILE';
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100279 var webStoreUrl = FileTasks.createWebStoreLink(extension, mimeType);
Ben Murdocheb525c52013-07-10 11:40:50 +0100280 var text = loadTimeData.getStringF(messageString,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100281 webStoreUrl,
282 FileTasks.NO_ACTION_FOR_FILE_URL);
283 this.fileManager_.alert.showHtml(filename, text, function() {});
284 }.bind(this));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000285 }
286 }.bind(this);
287
288 this.checkAvailability_(function() {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100289 chrome.fileBrowserPrivate.viewFiles(urls, callback);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000290 }.bind(this));
291 }
292
293 // Do nothing for multiple urls.
294};
295
296/**
297 * Executes a single task.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000298 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000299 * @param {string} taskId Task identifier.
300 * @param {Array.<string>=} opt_urls Urls to execute on instead of |this.urls_|.
301 * @private
302 */
303FileTasks.prototype.execute_ = function(taskId, opt_urls) {
304 var urls = opt_urls || this.urls_;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100305 FileTasks.recordViewingFileTypeUMA_(urls);
306 this.executeInternal_(taskId, urls);
307};
308
309/**
310 * The core implementation to execute a single task.
311 *
312 * @param {string} taskId Task identifier.
313 * @param {Array.<string>} urls Urls to execute.
314 * @private
315 */
316FileTasks.prototype.executeInternal_ = function(taskId, urls) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000317 this.checkAvailability_(function() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000318 var taskParts = taskId.split('|');
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100319 if (taskParts[0] == chrome.runtime.id && taskParts[1] == 'file') {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000320 // For internal tasks we do not listen to the event to avoid
321 // handling the same task instance from multiple tabs.
322 // So, we manually execute the task.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000323 this.executeInternalTask_(taskParts[2], urls);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000324 } else {
325 chrome.fileBrowserPrivate.executeTask(taskId, urls);
326 }
327 }.bind(this));
328};
329
330/**
331 * Checks whether the remote files are available right now.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000332 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000333 * @param {function} callback The callback.
334 * @private
335 */
336FileTasks.prototype.checkAvailability_ = function(callback) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000337 var areAll = function(props, name) {
338 var isOne = function(e) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000339 // If got no properties, we safely assume that item is unavailable.
340 return e && e[name];
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000341 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000342 return props.filter(isOne).length == props.length;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000343 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000344
345 var fm = this.fileManager_;
346 var urls = this.urls_;
347
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000348 if (fm.isOnDrive() && fm.isDriveOffline()) {
349 fm.metadataCache_.get(urls, 'drive', function(props) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000350 if (areAll(props, 'availableOffline')) {
351 callback();
352 return;
353 }
354
355 fm.alert.showHtml(
356 loadTimeData.getString('OFFLINE_HEADER'),
357 props[0].hosted ?
358 loadTimeData.getStringF(
359 urls.length == 1 ?
360 'HOSTED_OFFLINE_MESSAGE' :
361 'HOSTED_OFFLINE_MESSAGE_PLURAL') :
362 loadTimeData.getStringF(
363 urls.length == 1 ?
364 'OFFLINE_MESSAGE' :
365 'OFFLINE_MESSAGE_PLURAL',
366 loadTimeData.getString('OFFLINE_COLUMN_LABEL')));
367 });
368 return;
369 }
370
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000371 if (fm.isOnDrive() && fm.isDriveOnMeteredConnection()) {
372 fm.metadataCache_.get(urls, 'drive', function(driveProps) {
373 if (areAll(driveProps, 'availableWhenMetered')) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000374 callback();
375 return;
376 }
377
378 fm.metadataCache_.get(urls, 'filesystem', function(fileProps) {
379 var sizeToDownload = 0;
380 for (var i = 0; i != urls.length; i++) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000381 if (!driveProps[i].availableWhenMetered)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000382 sizeToDownload += fileProps[i].size;
383 }
384 fm.confirm.show(
385 loadTimeData.getStringF(
386 urls.length == 1 ?
387 'CONFIRM_MOBILE_DATA_USE' :
388 'CONFIRM_MOBILE_DATA_USE_PLURAL',
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000389 util.bytesToString(sizeToDownload)),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000390 callback);
391 });
392 });
393 return;
394 }
395
396 callback();
397};
398
399/**
400 * Executes an internal task.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000401 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000402 * @param {string} id The short task id.
403 * @param {Array.<string>} urls The urls to execute on.
404 * @private
405 */
406FileTasks.prototype.executeInternalTask_ = function(id, urls) {
407 var fm = this.fileManager_;
408
409 if (id == 'play') {
410 var position = 0;
411 if (urls.length == 1) {
412 // If just a single audio file is selected pass along every audio file
413 // in the directory.
414 var selectedUrl = urls[0];
415 urls = fm.getAllUrlsInCurrentDirectory().filter(FileType.isAudio);
416 position = urls.indexOf(selectedUrl);
417 }
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100418 chrome.runtime.getBackgroundPage(function(background) {
419 background.launchAudioPlayer({ items: urls, position: position });
420 });
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000421 return;
422 }
423
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100424 if (id == 'watch') {
425 console.assert(urls.length == 1, 'Cannot open multiple videos');
426 chrome.runtime.getBackgroundPage(function(background) {
427 background.launchVideoPlayer(urls[0]);
428 });
429 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000430 }
431
432 if (id == 'mount-archive') {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100433 this.mountArchivesInternal_(urls);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000434 return;
435 }
436
437 if (id == 'format-device') {
438 fm.confirm.show(loadTimeData.getString('FORMATTING_WARNING'), function() {
439 chrome.fileBrowserPrivate.formatDevice(urls[0]);
440 });
441 return;
442 }
443
444 if (id == 'gallery') {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100445 this.openGalleryInternal_(urls);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000446 return;
447 }
448
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000449 if (id == 'view-pdf' || id == 'view-swf' || id == 'view-in-browser' ||
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000450 id == 'install-crx' || id.match(/^open-hosted-/) || id == 'watch') {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100451 chrome.fileBrowserPrivate.viewFiles(urls, function(success) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000452 if (!success)
453 console.error('chrome.fileBrowserPrivate.viewFiles failed', urls);
454 });
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000455 }
456};
457
458/**
459 * Mounts archives.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000460 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000461 * @param {Array.<string>} urls Mount file urls list.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100462 */
463FileTasks.prototype.mountArchives = function(urls) {
464 FileTasks.recordViewingFileTypeUMA_(urls);
465 this.mountArchivesInternal_(urls);
466};
467
468/**
469 * The core implementation of mounts archives.
470 *
471 * @param {Array.<string>} urls Mount file urls list.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000472 * @private
473 */
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100474FileTasks.prototype.mountArchivesInternal_ = function(urls) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000475 var fm = this.fileManager_;
476
477 var tracker = fm.directoryModel_.createDirectoryChangeTracker();
478 tracker.start();
479
480 fm.resolveSelectResults_(urls, function(urls) {
481 for (var index = 0; index < urls.length; ++index) {
482 fm.volumeManager_.mountArchive(urls[index], function(mountPath) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000483 tracker.stop();
484 if (!tracker.hasChanged)
485 fm.directoryModel_.changeDirectory(mountPath);
486 }, function(url, error) {
487 var path = util.extractFilePath(url);
488 tracker.stop();
489 var namePos = path.lastIndexOf('/');
490 fm.alert.show(strf('ARCHIVE_MOUNT_FAILED',
491 path.substr(namePos + 1), error));
492 }.bind(null, urls[index]));
493 }
494 });
495};
496
497/**
498 * Open the Gallery.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000499 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000500 * @param {Array.<string>} urls List of selected urls.
501 */
502FileTasks.prototype.openGallery = function(urls) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100503 FileTasks.recordViewingFileTypeUMA_(urls);
504 this.openGalleryInternal_(urls);
505};
506
507/**
508 * The core implementation to open the Gallery.
509 *
510 * @param {Array.<string>} urls List of selected urls.
511 * @private
512 */
513FileTasks.prototype.openGalleryInternal_ = function(urls) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000514 var fm = this.fileManager_;
515
516 var allUrls =
517 fm.getAllUrlsInCurrentDirectory().filter(FileType.isImageOrVideo);
518
519 var galleryFrame = fm.document_.createElement('iframe');
520 galleryFrame.className = 'overlay-pane';
521 galleryFrame.scrolling = 'no';
522 galleryFrame.setAttribute('webkitallowfullscreen', true);
523
524 if (this.params_ && this.params_.gallery) {
525 // Remove the Gallery state from the location, we do not need it any more.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100526 util.updateAppState(null /* keep path */, '' /* remove search. */);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000527 }
528
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000529 var savedAppState = window.appState;
530 var savedTitle = document.title;
531
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000532 // Push a temporary state which will be replaced every time the selection
533 // changes in the Gallery and popped when the Gallery is closed.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100534 util.updateAppState();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000535
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100536 var onBack = function(selectedUrls) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000537 fm.directoryModel_.selectUrls(selectedUrls);
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100538 fm.closeFilePopup_(); // Will call Gallery.unload.
539 window.appState = savedAppState;
540 util.saveAppState();
541 document.title = savedTitle;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000542 };
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000543
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100544 var onClose = function() {
545 fm.onClose();
546 };
547
548 var onMaximize = function() {
549 fm.onMaximize();
550 };
551
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000552 galleryFrame.onload = function() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000553 galleryFrame.contentWindow.ImageUtil.metrics = metrics;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000554 window.galleryTestAPI = galleryFrame.contentWindow.galleryTestAPI;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000555
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000556 // TODO(haruki): isOnReadonlyDirectory() only checks the permission for the
557 // root. We should check more granular permission to know whether the file
558 // is writable or not.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000559 var readonly = fm.isOnReadonlyDirectory();
560 var currentDir = fm.directoryModel_.getCurrentDirEntry();
561 var downloadsDir = fm.directoryModel_.getRootsList().item(0);
562 var readonlyDirName = null;
563 if (readonly) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000564 readonlyDirName = fm.isOnDrive() ?
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000565 PathUtil.getRootLabel(PathUtil.getRootPath(currentDir.fullPath)) :
566 fm.directoryModel_.getCurrentRootName();
567 }
568
569 var context = {
570 // We show the root label in readonly warning (e.g. archive name).
571 readonlyDirName: readonlyDirName,
572 curDirEntry: currentDir,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000573 saveDirEntry: readonly ? downloadsDir : null,
574 searchResults: fm.directoryModel_.isSearching(),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000575 metadataCache: fm.metadataCache_,
576 pageState: this.params_,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100577 appWindow: chrome.app.window.current(),
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100578 onBack: onBack,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000579 onClose: onClose,
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100580 onMaximize: onMaximize,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000581 displayStringFunction: strf
582 };
583 galleryFrame.contentWindow.Gallery.open(context, allUrls, urls);
584 }.bind(this);
585
586 galleryFrame.src = 'gallery.html';
587 fm.openFilePopup_(galleryFrame, fm.updateTitle_.bind(fm));
588};
589
590/**
591 * Displays the list of tasks in a task picker combobutton.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000592 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000593 * @param {cr.ui.ComboButton} combobutton The task picker element.
594 * @private
595 */
596FileTasks.prototype.display_ = function(combobutton) {
597 if (this.tasks_.length == 0) {
598 combobutton.hidden = true;
599 return;
600 }
601
602 combobutton.clear();
603 combobutton.hidden = false;
604 combobutton.defaultItem = this.createCombobuttonItem_(this.defaultTask_);
605
606 var items = this.createItems_();
607
608 if (items.length > 1) {
609 var defaultIdx = 0;
610
611 for (var j = 0; j < items.length; j++) {
612 combobutton.addDropDownItem(items[j]);
613 if (items[j].task.taskId == this.defaultTask_.taskId)
614 defaultIdx = j;
615 }
616
617 combobutton.addSeparator();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100618 var changeDefaultMenuItem = combobutton.addDropDownItem({
619 label: loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM')
620 });
621 changeDefaultMenuItem.classList.add('change-default');
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000622 }
623};
624
625/**
626 * Creates sorted array of available task descriptions such as title and icon.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000627 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000628 * @return {Array} created array can be used to feed combobox, menus and so on.
629 * @private
630 */
631FileTasks.prototype.createItems_ = function() {
632 var items = [];
633 var title = this.defaultTask_.title + ' ' +
634 loadTimeData.getString('DEFAULT_ACTION_LABEL');
635 items.push(this.createCombobuttonItem_(this.defaultTask_, title, true));
636
637 for (var index = 0; index < this.tasks_.length; index++) {
638 var task = this.tasks_[index];
639 if (task != this.defaultTask_)
640 items.push(this.createCombobuttonItem_(task));
641 }
642
643 items.sort(function(a, b) {
644 return a.label.localeCompare(b.label);
645 });
646
647 return items;
648};
649
650/**
651 * Updates context menu with default item.
652 * @private
653 */
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000654
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000655FileTasks.prototype.updateMenuItem_ = function() {
656 this.fileManager_.updateContextMenuActionItems(this.defaultTask_,
657 this.tasks_.length > 1);
658};
659
660/**
661 * Creates combobutton item based on task.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000662 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000663 * @param {Object} task Task to convert.
664 * @param {string=} opt_title Title.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000665 * @param {boolean=} opt_bold Make a menu item bold.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000666 * @return {Object} Item appendable to combobutton drop-down list.
667 * @private
668 */
669FileTasks.prototype.createCombobuttonItem_ = function(task, opt_title,
670 opt_bold) {
671 return {
672 label: opt_title || task.title,
673 iconUrl: task.iconUrl,
674 iconType: task.iconType,
675 task: task,
676 bold: opt_bold || false
677 };
678};
679
680
681/**
682 * Decorates a FileTasks method, so it will be actually executed after the tasks
683 * are available.
684 * This decorator expects an implementation called |method + '_'|.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000685 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000686 * @param {string} method The method name.
687 */
688FileTasks.decorate = function(method) {
689 var privateMethod = method + '_';
690 FileTasks.prototype[method] = function() {
691 if (this.tasks_) {
692 this[privateMethod].apply(this, arguments);
693 } else {
694 this.pendingInvocations_.push([privateMethod, arguments]);
695 }
696 return this;
697 };
698};
699
700/**
701 * Shows modal action picker dialog with currently available list of tasks.
702 *
703 * @param {DefaultActionDialog} actionDialog Action dialog to show and update.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000704 * @param {string} title Title to use.
705 * @param {string} message Message to use.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000706 * @param {function(Object)} onSuccess Callback to pass selected task.
707 */
708FileTasks.prototype.showTaskPicker = function(actionDialog, title, message,
709 onSuccess) {
710 var items = this.createItems_();
711
712 var defaultIdx = 0;
713 for (var j = 0; j < items.length; j++) {
714 if (items[j].task.taskId == this.defaultTask_.taskId)
715 defaultIdx = j;
716 }
717
718 actionDialog.show(
719 title,
720 message,
721 items, defaultIdx,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100722 function(item) {
723 onSuccess(item.task);
724 });
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000725};
726
727FileTasks.decorate('display');
728FileTasks.decorate('updateMenuItem');
729FileTasks.decorate('execute');
730FileTasks.decorate('executeDefault');