OLD | NEW |
| (Empty) |
1 // 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 | |
5 'use strict'; | |
6 | |
7 /** | |
8 * This object encapsulates everything related to tasks execution. | |
9 * | |
10 * TODO(hirono): Pass each component instead of the entire FileManager. | |
11 * @param {FileManager} fileManager FileManager instance. | |
12 * @param {Object=} opt_params File manager load parameters. | |
13 * @constructor | |
14 */ | |
15 function FileTasks(fileManager, opt_params) { | |
16 this.fileManager_ = fileManager; | |
17 this.params_ = opt_params; | |
18 this.tasks_ = null; | |
19 this.defaultTask_ = null; | |
20 this.entries_ = null; | |
21 | |
22 /** | |
23 * List of invocations to be called once tasks are available. | |
24 * | |
25 * @private | |
26 * @type {Array.<Object>} | |
27 */ | |
28 this.pendingInvocations_ = []; | |
29 } | |
30 | |
31 /** | |
32 * Location of the Chrome Web Store. | |
33 * | |
34 * @const | |
35 * @type {string} | |
36 */ | |
37 FileTasks.CHROME_WEB_STORE_URL = 'https://chrome.google.com/webstore'; | |
38 | |
39 /** | |
40 * Base URL of apps list in the Chrome Web Store. This constant is used in | |
41 * FileTasks.createWebStoreLink(). | |
42 * | |
43 * @const | |
44 * @type {string} | |
45 */ | |
46 FileTasks.WEB_STORE_HANDLER_BASE_URL = | |
47 'https://chrome.google.com/webstore/category/collection/file_handlers'; | |
48 | |
49 | |
50 /** | |
51 * The app ID of the video player app. | |
52 * @const | |
53 * @type {string} | |
54 */ | |
55 FileTasks.VIDEO_PLAYER_ID = 'jcgeabjmjgoblfofpppfkcoakmfobdko'; | |
56 | |
57 /** | |
58 * Returns URL of the Chrome Web Store which show apps supporting the given | |
59 * file-extension and mime-type. | |
60 * | |
61 * @param {string} extension Extension of the file (with the first dot). | |
62 * @param {string} mimeType Mime type of the file. | |
63 * @return {string} URL | |
64 */ | |
65 FileTasks.createWebStoreLink = function(extension, mimeType) { | |
66 if (!extension) | |
67 return FileTasks.CHROME_WEB_STORE_URL; | |
68 | |
69 if (extension[0] === '.') | |
70 extension = extension.substr(1); | |
71 else | |
72 console.warn('Please pass an extension with a dot to createWebStoreLink.'); | |
73 | |
74 var url = FileTasks.WEB_STORE_HANDLER_BASE_URL; | |
75 url += '?_fe=' + extension.toLowerCase().replace(/[^\w]/g, ''); | |
76 | |
77 // If a mime is given, add it into the URL. | |
78 if (mimeType) | |
79 url += '&_fmt=' + mimeType.replace(/[^-\w\/]/g, ''); | |
80 return url; | |
81 }; | |
82 | |
83 /** | |
84 * Complete the initialization. | |
85 * | |
86 * @param {Array.<Entry>} entries List of file entries. | |
87 * @param {Array.<string>=} opt_mimeTypes List of MIME types for each | |
88 * of the files. | |
89 */ | |
90 FileTasks.prototype.init = function(entries, opt_mimeTypes) { | |
91 this.entries_ = entries; | |
92 this.mimeTypes_ = opt_mimeTypes || []; | |
93 | |
94 // TODO(mtomasz): Move conversion from entry to url to custom bindings. | |
95 var urls = util.entriesToURLs(entries); | |
96 if (urls.length > 0) { | |
97 chrome.fileBrowserPrivate.getFileTasks(urls, this.mimeTypes_, | |
98 this.onTasks_.bind(this)); | |
99 } | |
100 }; | |
101 | |
102 /** | |
103 * Returns amount of tasks. | |
104 * | |
105 * @return {number} amount of tasks. | |
106 */ | |
107 FileTasks.prototype.size = function() { | |
108 return (this.tasks_ && this.tasks_.length) || 0; | |
109 }; | |
110 | |
111 /** | |
112 * Callback when tasks found. | |
113 * | |
114 * @param {Array.<Object>} tasks The tasks. | |
115 * @private | |
116 */ | |
117 FileTasks.prototype.onTasks_ = function(tasks) { | |
118 this.processTasks_(tasks); | |
119 for (var index = 0; index < this.pendingInvocations_.length; index++) { | |
120 var name = this.pendingInvocations_[index][0]; | |
121 var args = this.pendingInvocations_[index][1]; | |
122 this[name].apply(this, args); | |
123 } | |
124 this.pendingInvocations_ = []; | |
125 }; | |
126 | |
127 /** | |
128 * The list of known extensions to record UMA. | |
129 * Note: Because the data is recorded by the index, so new item shouldn't be | |
130 * inserted. | |
131 * | |
132 * @const | |
133 * @type {Array.<string>} | |
134 * @private | |
135 */ | |
136 FileTasks.UMA_INDEX_KNOWN_EXTENSIONS_ = Object.freeze([ | |
137 'other', '.3ga', '.3gp', '.aac', '.alac', '.asf', '.avi', '.bmp', '.csv', | |
138 '.doc', '.docx', '.flac', '.gif', '.jpeg', '.jpg', '.log', '.m3u', '.m3u8', | |
139 '.m4a', '.m4v', '.mid', '.mkv', '.mov', '.mp3', '.mp4', '.mpg', '.odf', | |
140 '.odp', '.ods', '.odt', '.oga', '.ogg', '.ogv', '.pdf', '.png', '.ppt', | |
141 '.pptx', '.ra', '.ram', '.rar', '.rm', '.rtf', '.wav', '.webm', '.webp', | |
142 '.wma', '.wmv', '.xls', '.xlsx', | |
143 ]); | |
144 | |
145 /** | |
146 * The list of executable file extensions. | |
147 * | |
148 * @const | |
149 * @type {Array.<string>} | |
150 */ | |
151 FileTasks.EXECUTABLE_EXTENSIONS = Object.freeze([ | |
152 '.exe', '.lnk', '.deb', '.dmg', '.jar', '.msi', | |
153 ]); | |
154 | |
155 /** | |
156 * The list of extensions to skip the suggest app dialog. | |
157 * @const | |
158 * @type {Array.<string>} | |
159 * @private | |
160 */ | |
161 FileTasks.EXTENSIONS_TO_SKIP_SUGGEST_APPS_ = Object.freeze([ | |
162 '.crdownload', '.dsc', '.inf', '.crx', | |
163 ]); | |
164 | |
165 /** | |
166 * Records trial of opening file grouped by extensions. | |
167 * | |
168 * @param {Array.<Entry>} entries The entries to be opened. | |
169 * @private | |
170 */ | |
171 FileTasks.recordViewingFileTypeUMA_ = function(entries) { | |
172 for (var i = 0; i < entries.length; i++) { | |
173 var entry = entries[i]; | |
174 var extension = FileType.getExtension(entry).toLowerCase(); | |
175 if (FileTasks.UMA_INDEX_KNOWN_EXTENSIONS_.indexOf(extension) < 0) { | |
176 extension = 'other'; | |
177 } | |
178 metrics.recordEnum( | |
179 'ViewingFileType', extension, FileTasks.UMA_INDEX_KNOWN_EXTENSIONS_); | |
180 } | |
181 }; | |
182 | |
183 /** | |
184 * Returns true if the taskId is for an internal task. | |
185 * | |
186 * @param {string} taskId Task identifier. | |
187 * @return {boolean} True if the task ID is for an internal task. | |
188 * @private | |
189 */ | |
190 FileTasks.isInternalTask_ = function(taskId) { | |
191 var taskParts = taskId.split('|'); | |
192 var appId = taskParts[0]; | |
193 var taskType = taskParts[1]; | |
194 var actionId = taskParts[2]; | |
195 // The action IDs here should match ones used in executeInternalTask_(). | |
196 return (appId === chrome.runtime.id && | |
197 taskType === 'file' && | |
198 (actionId === 'play' || | |
199 actionId === 'mount-archive' || | |
200 actionId === 'gallery' || | |
201 actionId === 'gallery-video')); | |
202 }; | |
203 | |
204 /** | |
205 * Processes internal tasks. | |
206 * | |
207 * @param {Array.<Object>} tasks The tasks. | |
208 * @private | |
209 */ | |
210 FileTasks.prototype.processTasks_ = function(tasks) { | |
211 this.tasks_ = []; | |
212 var id = chrome.runtime.id; | |
213 var isOnDrive = false; | |
214 var fm = this.fileManager_; | |
215 for (var index = 0; index < this.entries_.length; ++index) { | |
216 var locationInfo = fm.volumeManager.getLocationInfo(this.entries_[index]); | |
217 if (locationInfo && locationInfo.isDriveBased) { | |
218 isOnDrive = true; | |
219 break; | |
220 } | |
221 } | |
222 | |
223 for (var i = 0; i < tasks.length; i++) { | |
224 var task = tasks[i]; | |
225 var taskParts = task.taskId.split('|'); | |
226 | |
227 // Skip internal Files.app's handlers. | |
228 if (taskParts[0] === id && (taskParts[2] === 'auto-open' || | |
229 taskParts[2] === 'select' || taskParts[2] === 'open')) { | |
230 continue; | |
231 } | |
232 | |
233 // Tweak images, titles of internal tasks. | |
234 if (taskParts[0] === id && taskParts[1] === 'file') { | |
235 if (taskParts[2] === 'play') { | |
236 // TODO(serya): This hack needed until task.iconUrl is working | |
237 // (see GetFileTasksFileBrowserFunction::RunImpl). | |
238 task.iconType = 'audio'; | |
239 task.title = loadTimeData.getString('ACTION_LISTEN'); | |
240 } else if (taskParts[2] === 'mount-archive') { | |
241 task.iconType = 'archive'; | |
242 task.title = loadTimeData.getString('MOUNT_ARCHIVE'); | |
243 } else if (taskParts[2] === 'gallery' || | |
244 taskParts[2] === 'gallery-video') { | |
245 task.iconType = 'image'; | |
246 task.title = loadTimeData.getString('ACTION_OPEN'); | |
247 } else if (taskParts[2] === 'open-hosted-generic') { | |
248 if (this.entries_.length > 1) | |
249 task.iconType = 'generic'; | |
250 else // Use specific icon. | |
251 task.iconType = FileType.getIcon(this.entries_[0]); | |
252 task.title = loadTimeData.getString('ACTION_OPEN'); | |
253 } else if (taskParts[2] === 'open-hosted-gdoc') { | |
254 task.iconType = 'gdoc'; | |
255 task.title = loadTimeData.getString('ACTION_OPEN_GDOC'); | |
256 } else if (taskParts[2] === 'open-hosted-gsheet') { | |
257 task.iconType = 'gsheet'; | |
258 task.title = loadTimeData.getString('ACTION_OPEN_GSHEET'); | |
259 } else if (taskParts[2] === 'open-hosted-gslides') { | |
260 task.iconType = 'gslides'; | |
261 task.title = loadTimeData.getString('ACTION_OPEN_GSLIDES'); | |
262 } else if (taskParts[2] === 'view-swf') { | |
263 // Do not render this task if disabled. | |
264 if (!loadTimeData.getBoolean('SWF_VIEW_ENABLED')) | |
265 continue; | |
266 task.iconType = 'generic'; | |
267 task.title = loadTimeData.getString('ACTION_VIEW'); | |
268 } else if (taskParts[2] === 'view-pdf') { | |
269 // Do not render this task if disabled. | |
270 if (!loadTimeData.getBoolean('PDF_VIEW_ENABLED')) | |
271 continue; | |
272 task.iconType = 'pdf'; | |
273 task.title = loadTimeData.getString('ACTION_VIEW'); | |
274 } else if (taskParts[2] === 'view-in-browser') { | |
275 task.iconType = 'generic'; | |
276 task.title = loadTimeData.getString('ACTION_VIEW'); | |
277 } | |
278 } | |
279 | |
280 if (!task.iconType && taskParts[1] === 'web-intent') { | |
281 task.iconType = 'generic'; | |
282 } | |
283 | |
284 this.tasks_.push(task); | |
285 if (this.defaultTask_ === null && task.isDefault) { | |
286 this.defaultTask_ = task; | |
287 } | |
288 } | |
289 if (!this.defaultTask_ && this.tasks_.length > 0) { | |
290 // If we haven't picked a default task yet, then just pick the first one. | |
291 // This is not the preferred way we want to pick this, but better this than | |
292 // no default at all if the C++ code didn't set one. | |
293 this.defaultTask_ = this.tasks_[0]; | |
294 } | |
295 }; | |
296 | |
297 /** | |
298 * Executes default task. | |
299 * | |
300 * @param {function(boolean, Array.<string>)=} opt_callback Called when the | |
301 * default task is executed, or the error is occurred. | |
302 * @private | |
303 */ | |
304 FileTasks.prototype.executeDefault_ = function(opt_callback) { | |
305 FileTasks.recordViewingFileTypeUMA_(this.entries_); | |
306 this.executeDefaultInternal_(this.entries_, opt_callback); | |
307 }; | |
308 | |
309 /** | |
310 * Executes default task. | |
311 * | |
312 * @param {Array.<Entry>} entries Entries to execute. | |
313 * @param {function(boolean, Array.<Entry>)=} opt_callback Called when the | |
314 * default task is executed, or the error is occurred. | |
315 * @private | |
316 */ | |
317 FileTasks.prototype.executeDefaultInternal_ = function(entries, opt_callback) { | |
318 var callback = opt_callback || function(arg1, arg2) {}; | |
319 | |
320 if (this.defaultTask_ !== null) { | |
321 this.executeInternal_(this.defaultTask_.taskId, entries); | |
322 callback(true, entries); | |
323 return; | |
324 } | |
325 | |
326 // We don't have tasks, so try to show a file in a browser tab. | |
327 // We only do that for single selection to avoid confusion. | |
328 if (entries.length !== 1 || !entries[0]) | |
329 return; | |
330 | |
331 var filename = entries[0].name; | |
332 var extension = PathUtil.splitExtension(filename)[1]; | |
333 var mimeType = this.mimeTypes_[0]; | |
334 | |
335 var showAlert = function() { | |
336 var textMessageId; | |
337 var titleMessageId; | |
338 switch (extension) { | |
339 case '.exe': | |
340 textMessageId = 'NO_ACTION_FOR_EXECUTABLE'; | |
341 break; | |
342 case '.crx': | |
343 textMessageId = 'NO_ACTION_FOR_CRX'; | |
344 titleMessageId = 'NO_ACTION_FOR_CRX_TITLE'; | |
345 break; | |
346 default: | |
347 textMessageId = 'NO_ACTION_FOR_FILE'; | |
348 } | |
349 | |
350 var webStoreUrl = FileTasks.createWebStoreLink(extension, mimeType); | |
351 var text = strf(textMessageId, webStoreUrl, str('NO_ACTION_FOR_FILE_URL')); | |
352 var title = titleMessageId ? str(titleMessageId) : filename; | |
353 this.fileManager_.alert.showHtml(title, text, function() {}); | |
354 callback(false, urls); | |
355 }.bind(this); | |
356 | |
357 var onViewFilesFailure = function() { | |
358 var fm = this.fileManager_; | |
359 if (!fm.isOnDrive() || | |
360 !entries[0] || | |
361 FileTasks.EXTENSIONS_TO_SKIP_SUGGEST_APPS_.indexOf(extension) !== -1) { | |
362 showAlert(); | |
363 return; | |
364 } | |
365 | |
366 fm.openSuggestAppsDialog( | |
367 entries[0], | |
368 function() { | |
369 var newTasks = new FileTasks(fm); | |
370 newTasks.init(entries, this.mimeTypes_); | |
371 newTasks.executeDefault(); | |
372 callback(true, entries); | |
373 }.bind(this), | |
374 // Cancelled callback. | |
375 function() { | |
376 callback(false, entries); | |
377 }, | |
378 showAlert); | |
379 }.bind(this); | |
380 | |
381 var onViewFiles = function(result) { | |
382 switch (result) { | |
383 case 'opened': | |
384 callback(success, entries); | |
385 break; | |
386 case 'message_sent': | |
387 util.isTeleported(window).then(function(teleported) { | |
388 if (teleported) { | |
389 util.showOpenInOtherDesktopAlert( | |
390 this.fileManager_.ui.alertDialog, entries); | |
391 } | |
392 }.bind(this)); | |
393 callback(success, entries); | |
394 break; | |
395 case 'empty': | |
396 callback(success, entries); | |
397 break; | |
398 case 'failed': | |
399 onViewFilesFailure(); | |
400 break; | |
401 } | |
402 }.bind(this); | |
403 | |
404 this.checkAvailability_(function() { | |
405 // TODO(mtomasz): Pass entries instead. | |
406 var urls = util.entriesToURLs(entries); | |
407 var taskId = chrome.runtime.id + '|file|view-in-browser'; | |
408 chrome.fileBrowserPrivate.executeTask(taskId, urls, onViewFiles); | |
409 }.bind(this)); | |
410 }; | |
411 | |
412 /** | |
413 * Executes a single task. | |
414 * | |
415 * @param {string} taskId Task identifier. | |
416 * @param {Array.<Entry>=} opt_entries Entries to xecute on instead of | |
417 * this.entries_|. | |
418 * @private | |
419 */ | |
420 FileTasks.prototype.execute_ = function(taskId, opt_entries) { | |
421 var entries = opt_entries || this.entries_; | |
422 FileTasks.recordViewingFileTypeUMA_(entries); | |
423 this.executeInternal_(taskId, entries); | |
424 }; | |
425 | |
426 /** | |
427 * The core implementation to execute a single task. | |
428 * | |
429 * @param {string} taskId Task identifier. | |
430 * @param {Array.<Entry>} entries Entries to execute. | |
431 * @private | |
432 */ | |
433 FileTasks.prototype.executeInternal_ = function(taskId, entries) { | |
434 this.checkAvailability_(function() { | |
435 if (FileTasks.isInternalTask_(taskId)) { | |
436 var taskParts = taskId.split('|'); | |
437 this.executeInternalTask_(taskParts[2], entries); | |
438 } else { | |
439 // TODO(mtomasz): Pass entries instead. | |
440 var urls = util.entriesToURLs(entries); | |
441 chrome.fileBrowserPrivate.executeTask(taskId, urls, function(result) { | |
442 if (result !== 'message_sent') | |
443 return; | |
444 util.isTeleported(window).then(function(teleported) { | |
445 if (teleported) { | |
446 util.showOpenInOtherDesktopAlert( | |
447 this.fileManager_.ui.alertDialog, entries); | |
448 } | |
449 }.bind(this)); | |
450 }.bind(this)); | |
451 } | |
452 }.bind(this)); | |
453 }; | |
454 | |
455 /** | |
456 * Checks whether the remote files are available right now. | |
457 * | |
458 * @param {function} callback The callback. | |
459 * @private | |
460 */ | |
461 FileTasks.prototype.checkAvailability_ = function(callback) { | |
462 var areAll = function(props, name) { | |
463 var isOne = function(e) { | |
464 // If got no properties, we safely assume that item is unavailable. | |
465 return e && e[name]; | |
466 }; | |
467 return props.filter(isOne).length === props.length; | |
468 }; | |
469 | |
470 var fm = this.fileManager_; | |
471 var entries = this.entries_; | |
472 | |
473 var isDriveOffline = fm.volumeManager.getDriveConnectionState().type === | |
474 util.DriveConnectionType.OFFLINE; | |
475 | |
476 if (fm.isOnDrive() && isDriveOffline) { | |
477 fm.metadataCache_.get(entries, 'drive', function(props) { | |
478 if (areAll(props, 'availableOffline')) { | |
479 callback(); | |
480 return; | |
481 } | |
482 | |
483 fm.alert.showHtml( | |
484 loadTimeData.getString('OFFLINE_HEADER'), | |
485 props[0].hosted ? | |
486 loadTimeData.getStringF( | |
487 entries.length === 1 ? | |
488 'HOSTED_OFFLINE_MESSAGE' : | |
489 'HOSTED_OFFLINE_MESSAGE_PLURAL') : | |
490 loadTimeData.getStringF( | |
491 entries.length === 1 ? | |
492 'OFFLINE_MESSAGE' : | |
493 'OFFLINE_MESSAGE_PLURAL', | |
494 loadTimeData.getString('OFFLINE_COLUMN_LABEL'))); | |
495 }); | |
496 return; | |
497 } | |
498 | |
499 var isOnMetered = fm.volumeManager.getDriveConnectionState().type === | |
500 util.DriveConnectionType.METERED; | |
501 | |
502 if (fm.isOnDrive() && isOnMetered) { | |
503 fm.metadataCache_.get(entries, 'drive', function(driveProps) { | |
504 if (areAll(driveProps, 'availableWhenMetered')) { | |
505 callback(); | |
506 return; | |
507 } | |
508 | |
509 fm.metadataCache_.get(entries, 'filesystem', function(fileProps) { | |
510 var sizeToDownload = 0; | |
511 for (var i = 0; i !== entries.length; i++) { | |
512 if (!driveProps[i].availableWhenMetered) | |
513 sizeToDownload += fileProps[i].size; | |
514 } | |
515 fm.confirm.show( | |
516 loadTimeData.getStringF( | |
517 entries.length === 1 ? | |
518 'CONFIRM_MOBILE_DATA_USE' : | |
519 'CONFIRM_MOBILE_DATA_USE_PLURAL', | |
520 util.bytesToString(sizeToDownload)), | |
521 callback); | |
522 }); | |
523 }); | |
524 return; | |
525 } | |
526 | |
527 callback(); | |
528 }; | |
529 | |
530 /** | |
531 * Executes an internal task. | |
532 * | |
533 * @param {string} id The short task id. | |
534 * @param {Array.<Entry>} entries The entries to execute on. | |
535 * @private | |
536 */ | |
537 FileTasks.prototype.executeInternalTask_ = function(id, entries) { | |
538 var fm = this.fileManager_; | |
539 | |
540 if (id === 'play') { | |
541 var position = 0; | |
542 if (entries.length === 1) { | |
543 // If just a single audio file is selected pass along every audio file | |
544 // in the directory. | |
545 var selectedEntries = entries[0]; | |
546 entries = fm.getAllEntriesInCurrentDirectory().filter(FileType.isAudio); | |
547 position = entries.indexOf(selectedEntries); | |
548 } | |
549 // TODO(mtomasz): Pass entries instead. | |
550 var urls = util.entriesToURLs(entries); | |
551 chrome.fileBrowserPrivate.getProfiles(function(profiles, | |
552 currentId, | |
553 displayedId) { | |
554 fm.backgroundPage.launchAudioPlayer({items: urls, position: position}, | |
555 displayedId); | |
556 }); | |
557 return; | |
558 } | |
559 | |
560 if (id === 'mount-archive') { | |
561 this.mountArchivesInternal_(entries); | |
562 return; | |
563 } | |
564 | |
565 if (id === 'gallery' || id === 'gallery-video') { | |
566 this.openGalleryInternal_(entries); | |
567 return; | |
568 } | |
569 | |
570 console.error('Unexpected action ID: ' + id); | |
571 }; | |
572 | |
573 /** | |
574 * Mounts archives. | |
575 * | |
576 * @param {Array.<Entry>} entries Mount file entries list. | |
577 */ | |
578 FileTasks.prototype.mountArchives = function(entries) { | |
579 FileTasks.recordViewingFileTypeUMA_(entries); | |
580 this.mountArchivesInternal_(entries); | |
581 }; | |
582 | |
583 /** | |
584 * The core implementation of mounts archives. | |
585 * | |
586 * @param {Array.<Entry>} entries Mount file entries list. | |
587 * @private | |
588 */ | |
589 FileTasks.prototype.mountArchivesInternal_ = function(entries) { | |
590 var fm = this.fileManager_; | |
591 | |
592 var tracker = fm.directoryModel.createDirectoryChangeTracker(); | |
593 tracker.start(); | |
594 | |
595 // TODO(mtomasz): Pass Entries instead of URLs. | |
596 var urls = util.entriesToURLs(entries); | |
597 fm.resolveSelectResults_(urls, function(resolvedURLs) { | |
598 for (var index = 0; index < resolvedURLs.length; ++index) { | |
599 // TODO(mtomasz): Pass Entry instead of URL. | |
600 fm.volumeManager.mountArchive(resolvedURLs[index], | |
601 function(volumeInfo) { | |
602 if (tracker.hasChanged) { | |
603 tracker.stop(); | |
604 return; | |
605 } | |
606 volumeInfo.resolveDisplayRoot(function(displayRoot) { | |
607 if (tracker.hasChanged) { | |
608 tracker.stop(); | |
609 return; | |
610 } | |
611 fm.directoryModel.changeDirectoryEntry(displayRoot); | |
612 }, function() { | |
613 console.warn('Failed to resolve the display root after mounting.'); | |
614 tracker.stop(); | |
615 }); | |
616 }, function(url, error) { | |
617 tracker.stop(); | |
618 var path = util.extractFilePath(url); | |
619 var namePos = path.lastIndexOf('/'); | |
620 fm.alert.show(strf('ARCHIVE_MOUNT_FAILED', | |
621 path.substr(namePos + 1), error)); | |
622 }.bind(null, resolvedURLs[index])); | |
623 } | |
624 }); | |
625 }; | |
626 | |
627 /** | |
628 * Open the Gallery. | |
629 * | |
630 * @param {Array.<Entry>} entries List of selected entries. | |
631 */ | |
632 FileTasks.prototype.openGallery = function(entries) { | |
633 FileTasks.recordViewingFileTypeUMA_(entries); | |
634 this.openGalleryInternal_(entries); | |
635 }; | |
636 | |
637 /** | |
638 * The core implementation to open the Gallery. | |
639 * | |
640 * @param {Array.<Entry>} entries List of selected entries. | |
641 * @private | |
642 */ | |
643 FileTasks.prototype.openGalleryInternal_ = function(entries) { | |
644 var fm = this.fileManager_; | |
645 | |
646 var allEntries = | |
647 fm.getAllEntriesInCurrentDirectory().filter(FileType.isImageOrVideo); | |
648 | |
649 var galleryFrame = fm.document_.createElement('iframe'); | |
650 galleryFrame.className = 'overlay-pane'; | |
651 galleryFrame.scrolling = 'no'; | |
652 galleryFrame.setAttribute('webkitallowfullscreen', true); | |
653 | |
654 if (this.params_ && this.params_.gallery) { | |
655 // Remove the Gallery state from the location, we do not need it any more. | |
656 // TODO(mtomasz): Consider keeping the selection path. | |
657 util.updateAppState( | |
658 null, /* keep current directory */ | |
659 '', /* remove current selection */ | |
660 '' /* remove search. */); | |
661 } | |
662 | |
663 var savedAppState = JSON.parse(JSON.stringify(window.appState)); | |
664 var savedTitle = document.title; | |
665 | |
666 // Push a temporary state which will be replaced every time the selection | |
667 // changes in the Gallery and popped when the Gallery is closed. | |
668 util.updateAppState(); | |
669 | |
670 var onBack = function(selectedEntries) { | |
671 fm.directoryModel.selectEntries(selectedEntries); | |
672 fm.closeFilePopup(); // Will call Gallery.unload. | |
673 window.appState = savedAppState; | |
674 util.saveAppState(); | |
675 document.title = savedTitle; | |
676 }; | |
677 | |
678 var onAppRegionChanged = function(visible) { | |
679 fm.onFilePopupAppRegionChanged(visible); | |
680 }; | |
681 | |
682 galleryFrame.onload = function() { | |
683 galleryFrame.contentWindow.ImageUtil.metrics = metrics; | |
684 | |
685 // TODO(haruki): isOnReadonlyDirectory() only checks the permission for the | |
686 // root. We should check more granular permission to know whether the file | |
687 // is writable or not. | |
688 var readonly = fm.isOnReadonlyDirectory(); | |
689 var currentDir = fm.getCurrentDirectoryEntry(); | |
690 var downloadsVolume = | |
691 fm.volumeManager.getCurrentProfileVolumeInfo(RootType.DOWNLOADS); | |
692 var downloadsDir = downloadsVolume && downloadsVolume.fileSystem.root; | |
693 | |
694 // TODO(mtomasz): Pass Entry instead of localized name. Conversion to a | |
695 // display string should be done in gallery.js. | |
696 var readonlyDirName = null; | |
697 if (readonly && currentDir) | |
698 readonlyDirName = util.getEntryLabel(fm.volumeManager, currentDir); | |
699 | |
700 var context = { | |
701 // We show the root label in readonly warning (e.g. archive name). | |
702 readonlyDirName: readonlyDirName, | |
703 curDirEntry: currentDir, | |
704 saveDirEntry: readonly ? downloadsDir : null, | |
705 searchResults: fm.directoryModel.isSearching(), | |
706 metadataCache: fm.metadataCache_, | |
707 pageState: this.params_, | |
708 appWindow: chrome.app.window.current(), | |
709 onBack: onBack, | |
710 onClose: fm.onClose.bind(fm), | |
711 onMaximize: fm.onMaximize.bind(fm), | |
712 onMinimize: fm.onMinimize.bind(fm), | |
713 onAppRegionChanged: onAppRegionChanged, | |
714 loadTimeData: fm.backgroundPage.background.stringData | |
715 }; | |
716 galleryFrame.contentWindow.Gallery.open( | |
717 context, fm.volumeManager, allEntries, entries); | |
718 }.bind(this); | |
719 | |
720 galleryFrame.src = 'gallery.html'; | |
721 fm.openFilePopup(galleryFrame, fm.updateTitle_.bind(fm)); | |
722 }; | |
723 | |
724 /** | |
725 * Displays the list of tasks in a task picker combobutton. | |
726 * | |
727 * @param {cr.ui.ComboButton} combobutton The task picker element. | |
728 * @private | |
729 */ | |
730 FileTasks.prototype.display_ = function(combobutton) { | |
731 if (this.tasks_.length === 0) { | |
732 combobutton.hidden = true; | |
733 return; | |
734 } | |
735 | |
736 combobutton.clear(); | |
737 combobutton.hidden = false; | |
738 combobutton.defaultItem = this.createCombobuttonItem_(this.defaultTask_); | |
739 | |
740 var items = this.createItems_(); | |
741 | |
742 if (items.length > 1) { | |
743 var defaultIdx = 0; | |
744 | |
745 for (var j = 0; j < items.length; j++) { | |
746 combobutton.addDropDownItem(items[j]); | |
747 if (items[j].task.taskId === this.defaultTask_.taskId) | |
748 defaultIdx = j; | |
749 } | |
750 | |
751 combobutton.addSeparator(); | |
752 var changeDefaultMenuItem = combobutton.addDropDownItem({ | |
753 label: loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM') | |
754 }); | |
755 changeDefaultMenuItem.classList.add('change-default'); | |
756 } | |
757 }; | |
758 | |
759 /** | |
760 * Creates sorted array of available task descriptions such as title and icon. | |
761 * | |
762 * @return {Array} created array can be used to feed combobox, menus and so on. | |
763 * @private | |
764 */ | |
765 FileTasks.prototype.createItems_ = function() { | |
766 var items = []; | |
767 var title = this.defaultTask_.title + ' ' + | |
768 loadTimeData.getString('DEFAULT_ACTION_LABEL'); | |
769 items.push(this.createCombobuttonItem_(this.defaultTask_, title, true)); | |
770 | |
771 for (var index = 0; index < this.tasks_.length; index++) { | |
772 var task = this.tasks_[index]; | |
773 if (task !== this.defaultTask_) | |
774 items.push(this.createCombobuttonItem_(task)); | |
775 } | |
776 | |
777 items.sort(function(a, b) { | |
778 return a.label.localeCompare(b.label); | |
779 }); | |
780 | |
781 return items; | |
782 }; | |
783 | |
784 /** | |
785 * Updates context menu with default item. | |
786 * @private | |
787 */ | |
788 | |
789 FileTasks.prototype.updateMenuItem_ = function() { | |
790 this.fileManager_.updateContextMenuActionItems(this.defaultTask_, | |
791 this.tasks_.length > 1); | |
792 }; | |
793 | |
794 /** | |
795 * Creates combobutton item based on task. | |
796 * | |
797 * @param {Object} task Task to convert. | |
798 * @param {string=} opt_title Title. | |
799 * @param {boolean=} opt_bold Make a menu item bold. | |
800 * @return {Object} Item appendable to combobutton drop-down list. | |
801 * @private | |
802 */ | |
803 FileTasks.prototype.createCombobuttonItem_ = function(task, opt_title, | |
804 opt_bold) { | |
805 return { | |
806 label: opt_title || task.title, | |
807 iconUrl: task.iconUrl, | |
808 iconType: task.iconType, | |
809 task: task, | |
810 bold: opt_bold || false | |
811 }; | |
812 }; | |
813 | |
814 /** | |
815 * Shows modal action picker dialog with currently available list of tasks. | |
816 * | |
817 * @param {DefaultActionDialog} actionDialog Action dialog to show and update. | |
818 * @param {string} title Title to use. | |
819 * @param {string} message Message to use. | |
820 * @param {function(Object)} onSuccess Callback to pass selected task. | |
821 */ | |
822 FileTasks.prototype.showTaskPicker = function(actionDialog, title, message, | |
823 onSuccess) { | |
824 var items = this.createItems_(); | |
825 | |
826 var defaultIdx = 0; | |
827 for (var j = 0; j < items.length; j++) { | |
828 if (items[j].task.taskId === this.defaultTask_.taskId) | |
829 defaultIdx = j; | |
830 } | |
831 | |
832 actionDialog.show( | |
833 title, | |
834 message, | |
835 items, defaultIdx, | |
836 function(item) { | |
837 onSuccess(item.task); | |
838 }); | |
839 }; | |
840 | |
841 /** | |
842 * Decorates a FileTasks method, so it will be actually executed after the tasks | |
843 * are available. | |
844 * This decorator expects an implementation called |method + '_'|. | |
845 * | |
846 * @param {string} method The method name. | |
847 */ | |
848 FileTasks.decorate = function(method) { | |
849 var privateMethod = method + '_'; | |
850 FileTasks.prototype[method] = function() { | |
851 if (this.tasks_) { | |
852 this[privateMethod].apply(this, arguments); | |
853 } else { | |
854 this.pendingInvocations_.push([privateMethod, arguments]); | |
855 } | |
856 return this; | |
857 }; | |
858 }; | |
859 | |
860 FileTasks.decorate('display'); | |
861 FileTasks.decorate('updateMenuItem'); | |
862 FileTasks.decorate('execute'); | |
863 FileTasks.decorate('executeDefault'); | |
OLD | NEW |