Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: chrome/browser/resources/file_manager/background/js/volume_manager.js

Issue 247123002: Move Files.app files to ui/file_manager (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix the test failure on non-chromeos Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 * Represents each volume, such as "drive", "download directory", each "USB
9 * flush storage", or "mounted zip archive" etc.
10 *
11 * @param {util.VolumeType} volumeType The type of the volume.
12 * @param {string} volumeId ID of the volume.
13 * @param {DOMFileSystem} fileSystem The file system object for this volume.
14 * @param {string} error The error if an error is found.
15 * @param {string} deviceType The type of device ('usb'|'sd'|'optical'|'mobile'
16 * |'unknown') (as defined in chromeos/disks/disk_mount_manager.cc).
17 * Can be null.
18 * @param {boolean} isReadOnly True if the volume is read only.
19 * @param {!{displayName:string, isCurrentProfile:boolean}} profile Profile
20 * information.
21 * @param {string} label Label of the volume.
22 * @constructor
23 */
24 function VolumeInfo(
25 volumeType,
26 volumeId,
27 fileSystem,
28 error,
29 deviceType,
30 isReadOnly,
31 profile,
32 label) {
33 this.volumeType_ = volumeType;
34 this.volumeId_ = volumeId;
35 this.fileSystem_ = fileSystem;
36 this.label_ = label;
37 this.displayRoot_ = null;
38 this.fakeEntries_ = {};
39 this.displayRoot_ = null;
40 this.displayRootPromise_ = null;
41
42 if (volumeType === util.VolumeType.DRIVE) {
43 // TODO(mtomasz): Convert fake entries to DirectoryProvider.
44 this.fakeEntries_[RootType.DRIVE_OFFLINE] = {
45 isDirectory: true,
46 rootType: RootType.DRIVE_OFFLINE,
47 toURL: function() { return 'fake-entry://drive_offline' }
48 };
49 this.fakeEntries_[RootType.DRIVE_SHARED_WITH_ME] = {
50 isDirectory: true,
51 rootType: RootType.DRIVE_SHARED_WITH_ME,
52 toURL: function() { return 'fake-entry://drive_shared_with_me'; }
53 };
54 this.fakeEntries_[RootType.DRIVE_RECENT] = {
55 isDirectory: true,
56 rootType: RootType.DRIVE_RECENT,
57 toURL: function() { return 'fake-entry://drive_recent'; }
58 };
59 }
60
61 // Note: This represents if the mounting of the volume is successfully done
62 // or not. (If error is empty string, the mount is successfully done).
63 // TODO(hidehiko): Rename to make this more understandable.
64 this.error_ = error;
65 this.deviceType_ = deviceType;
66 this.isReadOnly_ = isReadOnly;
67 this.profile_ = Object.freeze(profile);
68
69 Object.seal(this);
70 }
71
72 VolumeInfo.prototype = {
73 /**
74 * @return {util.VolumeType} Volume type.
75 */
76 get volumeType() {
77 return this.volumeType_;
78 },
79 /**
80 * @return {string} Volume ID.
81 */
82 get volumeId() {
83 return this.volumeId_;
84 },
85 /**
86 * @return {DOMFileSystem} File system object.
87 */
88 get fileSystem() {
89 return this.fileSystem_;
90 },
91 /**
92 * @return {DirectoryEntry} Display root path. It is null before finishing to
93 * resolve the entry.
94 */
95 get displayRoot() {
96 return this.displayRoot_;
97 },
98 /**
99 * @return {Object.<string, Object>} Fake entries.
100 */
101 get fakeEntries() {
102 return this.fakeEntries_;
103 },
104 /**
105 * @return {string} Error identifier.
106 */
107 get error() {
108 return this.error_;
109 },
110 /**
111 * @return {string} Device type identifier.
112 */
113 get deviceType() {
114 return this.deviceType_;
115 },
116 /**
117 * @return {boolean} Whether read only or not.
118 */
119 get isReadOnly() {
120 return this.isReadOnly_;
121 },
122 /**
123 * @return {!{displayName:string, isCurrentProfile:boolean}} Profile data.
124 */
125 get profile() {
126 return this.profile_;
127 },
128 /**
129 * @return {string} Label for the volume.
130 */
131 get label() {
132 return this.label_;
133 }
134 };
135
136 /**
137 * Starts resolving the display root and obtains it. It may take long time for
138 * Drive. Once resolved, it is cached.
139 *
140 * @param {function(DirectoryEntry)} onSuccess Success callback with the
141 * display root directory as an argument.
142 * @param {function(FileError)} onFailure Failure callback.
143 */
144 VolumeInfo.prototype.resolveDisplayRoot = function(onSuccess, onFailure) {
145 if (!this.displayRootPromise_) {
146 // TODO(mtomasz): Do not add VolumeInfo which failed to resolve root, and
147 // remove this if logic. Call onSuccess() always, instead.
148 if (this.volumeType !== util.VolumeType.DRIVE) {
149 if (this.fileSystem_)
150 this.displayRootPromise_ = Promise.resolve(this.fileSystem_.root);
151 else
152 this.displayRootPromise_ = Promise.reject(this.error);
153 } else {
154 // For Drive, we need to resolve.
155 var displayRootURL = this.fileSystem_.root.toURL() + '/root';
156 this.displayRootPromise_ = new Promise(
157 webkitResolveLocalFileSystemURL.bind(null, displayRootURL));
158 }
159
160 // Store the obtained displayRoot.
161 this.displayRootPromise_.then(function(displayRoot) {
162 this.displayRoot_ = displayRoot;
163 }.bind(this));
164 }
165 this.displayRootPromise_.then(onSuccess, onFailure);
166 };
167
168 /**
169 * Utilities for volume manager implementation.
170 */
171 var volumeManagerUtil = {};
172
173 /**
174 * Throws an Error when the given error is not in util.VolumeError.
175 * @param {util.VolumeError} error Status string usually received from APIs.
176 */
177 volumeManagerUtil.validateError = function(error) {
178 for (var key in util.VolumeError) {
179 if (error === util.VolumeError[key])
180 return;
181 }
182
183 throw new Error('Invalid mount error: ' + error);
184 };
185
186 /**
187 * Builds the VolumeInfo data from VolumeMetadata.
188 * @param {VolumeMetadata} volumeMetadata Metadata instance for the volume.
189 * @param {function(VolumeInfo)} callback Called on completion.
190 */
191 volumeManagerUtil.createVolumeInfo = function(volumeMetadata, callback) {
192 var localizedLabel;
193 switch (volumeMetadata.volumeType) {
194 case util.VolumeType.DOWNLOADS:
195 localizedLabel = str('DOWNLOADS_DIRECTORY_LABEL');
196 break;
197 case util.VolumeType.DRIVE:
198 localizedLabel = str('DRIVE_DIRECTORY_LABEL');
199 break;
200 default:
201 localizedLabel = volumeMetadata.volumeId.split(':', 2)[1];
202 break;
203 }
204
205 chrome.fileBrowserPrivate.requestFileSystem(
206 volumeMetadata.volumeId,
207 function(fileSystem) {
208 // TODO(mtomasz): chrome.runtime.lastError should have error reason.
209 if (!fileSystem) {
210 console.error('File system not found: ' + volumeMetadata.volumeId);
211 callback(new VolumeInfo(
212 volumeMetadata.volumeType,
213 volumeMetadata.volumeId,
214 null, // File system is not found.
215 volumeMetadata.mountCondition,
216 volumeMetadata.deviceType,
217 volumeMetadata.isReadOnly,
218 volumeMetadata.profile,
219 localizedLabel));
220 return;
221 }
222 if (volumeMetadata.volumeType === util.VolumeType.DRIVE) {
223 // After file system is mounted, we "read" drive grand root
224 // entry at first. This triggers full feed fetch on background.
225 // Note: we don't need to handle errors here, because even if
226 // it fails, accessing to some path later will just become
227 // a fast-fetch and it re-triggers full-feed fetch.
228 fileSystem.root.createReader().readEntries(
229 function() { /* do nothing */ },
230 function(error) {
231 console.error(
232 'Triggering full feed fetch is failed: ' + error.name);
233 });
234 }
235 callback(new VolumeInfo(
236 volumeMetadata.volumeType,
237 volumeMetadata.volumeId,
238 fileSystem,
239 volumeMetadata.mountCondition,
240 volumeMetadata.deviceType,
241 volumeMetadata.isReadOnly,
242 volumeMetadata.profile,
243 localizedLabel));
244 });
245 };
246
247 /**
248 * The order of the volume list based on root type.
249 * @type {Array.<util.VolumeType>}
250 * @const
251 * @private
252 */
253 volumeManagerUtil.volumeListOrder_ = [
254 util.VolumeType.DRIVE,
255 util.VolumeType.DOWNLOADS,
256 util.VolumeType.ARCHIVE,
257 util.VolumeType.REMOVABLE,
258 util.VolumeType.MTP,
259 util.VolumeType.CLOUD_DEVICE
260 ];
261
262 /**
263 * Orders two volumes by volumeType and volumeId.
264 *
265 * The volumes at first are compared by volume type in the order of
266 * volumeListOrder_. Then they are compared by volume ID.
267 *
268 * @param {VolumeInfo} volumeInfo1 Volume info to be compared.
269 * @param {VolumeInfo} volumeInfo2 Volume info to be compared.
270 * @return {number} Returns -1 if volume1 < volume2, returns 1 if volume2 >
271 * volume1, returns 0 if volume1 === volume2.
272 * @private
273 */
274 volumeManagerUtil.compareVolumeInfo_ = function(volumeInfo1, volumeInfo2) {
275 var typeIndex1 =
276 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo1.volumeType);
277 var typeIndex2 =
278 volumeManagerUtil.volumeListOrder_.indexOf(volumeInfo2.volumeType);
279 if (typeIndex1 !== typeIndex2)
280 return typeIndex1 < typeIndex2 ? -1 : 1;
281 if (volumeInfo1.volumeId !== volumeInfo2.volumeId)
282 return volumeInfo1.volumeId < volumeInfo2.volumeId ? -1 : 1;
283 return 0;
284 };
285
286 /**
287 * The container of the VolumeInfo for each mounted volume.
288 * @constructor
289 */
290 function VolumeInfoList() {
291 var field = 'volumeType,volumeId';
292
293 /**
294 * Holds VolumeInfo instances.
295 * @type {cr.ui.ArrayDataModel}
296 * @private
297 */
298 this.model_ = new cr.ui.ArrayDataModel([]);
299 this.model_.setCompareFunction(field, volumeManagerUtil.compareVolumeInfo_);
300 this.model_.sort(field, 'asc');
301
302 Object.freeze(this);
303 }
304
305 VolumeInfoList.prototype = {
306 get length() { return this.model_.length; }
307 };
308
309 /**
310 * Adds the event listener to listen the change of volume info.
311 * @param {string} type The name of the event.
312 * @param {function(Event)} handler The handler for the event.
313 */
314 VolumeInfoList.prototype.addEventListener = function(type, handler) {
315 this.model_.addEventListener(type, handler);
316 };
317
318 /**
319 * Removes the event listener.
320 * @param {string} type The name of the event.
321 * @param {function(Event)} handler The handler to be removed.
322 */
323 VolumeInfoList.prototype.removeEventListener = function(type, handler) {
324 this.model_.removeEventListener(type, handler);
325 };
326
327 /**
328 * Adds the volumeInfo to the appropriate position. If there already exists,
329 * just replaces it.
330 * @param {VolumeInfo} volumeInfo The information of the new volume.
331 */
332 VolumeInfoList.prototype.add = function(volumeInfo) {
333 var index = this.findIndex(volumeInfo.volumeId);
334 if (index !== -1)
335 this.model_.splice(index, 1, volumeInfo);
336 else
337 this.model_.push(volumeInfo);
338 };
339
340 /**
341 * Removes the VolumeInfo having the given ID.
342 * @param {string} volumeId ID of the volume.
343 */
344 VolumeInfoList.prototype.remove = function(volumeId) {
345 var index = this.findIndex(volumeId);
346 if (index !== -1)
347 this.model_.splice(index, 1);
348 };
349
350 /**
351 * Obtains an index from the volume ID.
352 * @param {string} volumeId Volume ID.
353 * @return {number} Index of the volume.
354 */
355 VolumeInfoList.prototype.findIndex = function(volumeId) {
356 for (var i = 0; i < this.model_.length; i++) {
357 if (this.model_.item(i).volumeId === volumeId)
358 return i;
359 }
360 return -1;
361 };
362
363 /**
364 * Searches the information of the volume that contains the passed entry.
365 * @param {Entry|Object} entry Entry on the volume to be found.
366 * @return {VolumeInfo} The volume's information, or null if not found.
367 */
368 VolumeInfoList.prototype.findByEntry = function(entry) {
369 for (var i = 0; i < this.length; i++) {
370 var volumeInfo = this.item(i);
371 if (volumeInfo.fileSystem &&
372 util.isSameFileSystem(volumeInfo.fileSystem, entry.filesystem)) {
373 return volumeInfo;
374 }
375 // Additionally, check fake entries.
376 for (var key in volumeInfo.fakeEntries_) {
377 var fakeEntry = volumeInfo.fakeEntries_[key];
378 if (util.isSameEntry(fakeEntry, entry))
379 return volumeInfo;
380 }
381 }
382 return null;
383 };
384
385 /**
386 * @param {number} index The index of the volume in the list.
387 * @return {VolumeInfo} The VolumeInfo instance.
388 */
389 VolumeInfoList.prototype.item = function(index) {
390 return this.model_.item(index);
391 };
392
393 /**
394 * VolumeManager is responsible for tracking list of mounted volumes.
395 *
396 * @constructor
397 * @extends {cr.EventTarget}
398 */
399 function VolumeManager() {
400 /**
401 * The list of archives requested to mount. We will show contents once
402 * archive is mounted, but only for mounts from within this filebrowser tab.
403 * @type {Object.<string, Object>}
404 * @private
405 */
406 this.requests_ = {};
407
408 /**
409 * The list of VolumeInfo instances for each mounted volume.
410 * @type {VolumeInfoList}
411 */
412 this.volumeInfoList = new VolumeInfoList();
413
414 /**
415 * Queue for mounting.
416 * @type {AsyncUtil.Queue}
417 * @private
418 */
419 this.mountQueue_ = new AsyncUtil.Queue();
420
421 // The status should be merged into VolumeManager.
422 // TODO(hidehiko): Remove them after the migration.
423 this.driveConnectionState_ = {
424 type: util.DriveConnectionType.OFFLINE,
425 reason: util.DriveConnectionReason.NO_SERVICE
426 };
427
428 chrome.fileBrowserPrivate.onDriveConnectionStatusChanged.addListener(
429 this.onDriveConnectionStatusChanged_.bind(this));
430 this.onDriveConnectionStatusChanged_();
431 }
432
433 /**
434 * Invoked when the drive connection status is changed.
435 * @private_
436 */
437 VolumeManager.prototype.onDriveConnectionStatusChanged_ = function() {
438 chrome.fileBrowserPrivate.getDriveConnectionState(function(state) {
439 this.driveConnectionState_ = state;
440 cr.dispatchSimpleEvent(this, 'drive-connection-changed');
441 }.bind(this));
442 };
443
444 /**
445 * Returns the drive connection state.
446 * @return {util.DriveConnectionType} Connection type.
447 */
448 VolumeManager.prototype.getDriveConnectionState = function() {
449 return this.driveConnectionState_;
450 };
451
452 /**
453 * VolumeManager extends cr.EventTarget.
454 */
455 VolumeManager.prototype.__proto__ = cr.EventTarget.prototype;
456
457 /**
458 * Time in milliseconds that we wait a response for. If no response on
459 * mount/unmount received the request supposed failed.
460 */
461 VolumeManager.TIMEOUT = 15 * 60 * 1000;
462
463 /**
464 * Queue to run getInstance sequentially.
465 * @type {AsyncUtil.Queue}
466 * @private
467 */
468 VolumeManager.getInstanceQueue_ = new AsyncUtil.Queue();
469
470 /**
471 * The singleton instance of VolumeManager. Initialized by the first invocation
472 * of getInstance().
473 * @type {VolumeManager}
474 * @private
475 */
476 VolumeManager.instance_ = null;
477
478 /**
479 * Returns the VolumeManager instance asynchronously. If it is not created or
480 * under initialization, it will waits for the finish of the initialization.
481 * @param {function(VolumeManager)} callback Called with the VolumeManager
482 * instance.
483 */
484 VolumeManager.getInstance = function(callback) {
485 VolumeManager.getInstanceQueue_.run(function(continueCallback) {
486 if (VolumeManager.instance_) {
487 callback(VolumeManager.instance_);
488 continueCallback();
489 return;
490 }
491
492 VolumeManager.instance_ = new VolumeManager();
493 VolumeManager.instance_.initialize_(function() {
494 callback(VolumeManager.instance_);
495 continueCallback();
496 });
497 });
498 };
499
500 /**
501 * Initializes mount points.
502 * @param {function()} callback Called upon the completion of the
503 * initialization.
504 * @private
505 */
506 VolumeManager.prototype.initialize_ = function(callback) {
507 chrome.fileBrowserPrivate.getVolumeMetadataList(function(volumeMetadataList) {
508 // We must subscribe to the mount completed event in the callback of
509 // getVolumeMetadataList. crbug.com/330061.
510 // But volumes reported by onMountCompleted events must be added after the
511 // volumes in the volumeMetadataList are mounted. crbug.com/135477.
512 this.mountQueue_.run(function(inCallback) {
513 // Create VolumeInfo for each volume.
514 var group = new AsyncUtil.Group();
515 for (var i = 0; i < volumeMetadataList.length; i++) {
516 group.add(function(volumeMetadata, continueCallback) {
517 volumeManagerUtil.createVolumeInfo(
518 volumeMetadata,
519 function(volumeInfo) {
520 this.volumeInfoList.add(volumeInfo);
521 if (volumeMetadata.volumeType === util.VolumeType.DRIVE)
522 this.onDriveConnectionStatusChanged_();
523 continueCallback();
524 }.bind(this));
525 }.bind(this, volumeMetadataList[i]));
526 }
527 group.run(function() {
528 // Call the callback of the initialize function.
529 callback();
530 // Call the callback of AsyncQueue. Maybe it invokes callbacks
531 // registered by mountCompleted events.
532 inCallback();
533 });
534 }.bind(this));
535
536 chrome.fileBrowserPrivate.onMountCompleted.addListener(
537 this.onMountCompleted_.bind(this));
538 }.bind(this));
539 };
540
541 /**
542 * Event handler called when some volume was mounted or unmounted.
543 * @param {MountCompletedEvent} event Received event.
544 * @private
545 */
546 VolumeManager.prototype.onMountCompleted_ = function(event) {
547 this.mountQueue_.run(function(callback) {
548 switch (event.eventType) {
549 case 'mount':
550 var requestKey = this.makeRequestKey_(
551 'mount',
552 event.volumeMetadata.sourcePath);
553
554 var error = event.status === 'success' ? '' : event.status;
555 if (!error || event.status === 'error_unknown_filesystem') {
556 volumeManagerUtil.createVolumeInfo(
557 event.volumeMetadata,
558 function(volumeInfo) {
559 this.volumeInfoList.add(volumeInfo);
560 this.finishRequest_(requestKey, event.status, volumeInfo);
561
562 if (volumeInfo.volumeType === util.VolumeType.DRIVE) {
563 // Update the network connection status, because until the
564 // drive is initialized, the status is set to not ready.
565 // TODO(mtomasz): The connection status should be migrated
566 // into VolumeMetadata.
567 this.onDriveConnectionStatusChanged_();
568 }
569 callback();
570 }.bind(this));
571 } else {
572 console.warn('Failed to mount a volume: ' + event.status);
573 this.finishRequest_(requestKey, event.status);
574 callback();
575 }
576 break;
577
578 case 'unmount':
579 var volumeId = event.volumeMetadata.volumeId;
580 var status = event.status;
581 if (status === util.VolumeError.PATH_UNMOUNTED) {
582 console.warn('Volume already unmounted: ', volumeId);
583 status = 'success';
584 }
585 var requestKey = this.makeRequestKey_('unmount', volumeId);
586 var requested = requestKey in this.requests_;
587 var volumeInfoIndex =
588 this.volumeInfoList.findIndex(volumeId);
589 var volumeInfo = volumeInfoIndex !== -1 ?
590 this.volumeInfoList.item(volumeInfoIndex) : null;
591 if (event.status === 'success' && !requested && volumeInfo) {
592 console.warn('Mounted volume without a request: ' + volumeId);
593 var e = new Event('externally-unmounted');
594 e.volumeInfo = volumeInfo;
595 this.dispatchEvent(e);
596 }
597
598 this.finishRequest_(requestKey, status);
599 if (event.status === 'success')
600 this.volumeInfoList.remove(event.volumeMetadata.volumeId);
601 callback();
602 break;
603 }
604 }.bind(this));
605 };
606
607 /**
608 * Creates string to match mount events with requests.
609 * @param {string} requestType 'mount' | 'unmount'. TODO(hidehiko): Replace by
610 * enum.
611 * @param {string} argument Argument describing the request, eg. source file
612 * path of the archive to be mounted, or a volumeId for unmounting.
613 * @return {string} Key for |this.requests_|.
614 * @private
615 */
616 VolumeManager.prototype.makeRequestKey_ = function(requestType, argument) {
617 return requestType + ':' + argument;
618 };
619
620 /**
621 * @param {string} fileUrl File url to the archive file.
622 * @param {function(VolumeInfo)} successCallback Success callback.
623 * @param {function(util.VolumeError)} errorCallback Error callback.
624 */
625 VolumeManager.prototype.mountArchive = function(
626 fileUrl, successCallback, errorCallback) {
627 chrome.fileBrowserPrivate.addMount(fileUrl, function(sourcePath) {
628 console.info(
629 'Mount request: url=' + fileUrl + '; sourcePath=' + sourcePath);
630 var requestKey = this.makeRequestKey_('mount', sourcePath);
631 this.startRequest_(requestKey, successCallback, errorCallback);
632 }.bind(this));
633 };
634
635 /**
636 * Unmounts volume.
637 * @param {!VolumeInfo} volumeInfo Volume to be unmounted.
638 * @param {function()} successCallback Success callback.
639 * @param {function(util.VolumeError)} errorCallback Error callback.
640 */
641 VolumeManager.prototype.unmount = function(volumeInfo,
642 successCallback,
643 errorCallback) {
644 chrome.fileBrowserPrivate.removeMount(volumeInfo.volumeId);
645 var requestKey = this.makeRequestKey_('unmount', volumeInfo.volumeId);
646 this.startRequest_(requestKey, successCallback, errorCallback);
647 };
648
649 /**
650 * Obtains a volume info containing the passed entry.
651 * @param {Entry|Object} entry Entry on the volume to be returned. Can be fake.
652 * @return {VolumeInfo} The VolumeInfo instance or null if not found.
653 */
654 VolumeManager.prototype.getVolumeInfo = function(entry) {
655 return this.volumeInfoList.findByEntry(entry);
656 };
657
658 /**
659 * Obtains volume information of the current profile.
660 *
661 * @param {util.VolumeType} volumeType Volume type.
662 * @return {VolumeInfo} Volume info.
663 */
664 VolumeManager.prototype.getCurrentProfileVolumeInfo = function(volumeType) {
665 for (var i = 0; i < this.volumeInfoList.length; i++) {
666 var volumeInfo = this.volumeInfoList.item(i);
667 if (volumeInfo.profile.isCurrentProfile &&
668 volumeInfo.volumeType === volumeType)
669 return volumeInfo;
670 }
671 return null;
672 };
673
674 /**
675 * Obtains location information from an entry.
676 *
677 * @param {Entry|Object} entry File or directory entry. It can be a fake entry.
678 * @return {EntryLocation} Location information.
679 */
680 VolumeManager.prototype.getLocationInfo = function(entry) {
681 var volumeInfo = this.volumeInfoList.findByEntry(entry);
682 if (!volumeInfo)
683 return null;
684
685 if (util.isFakeEntry(entry)) {
686 return new EntryLocation(
687 volumeInfo,
688 entry.rootType,
689 true /* the entry points a root directory. */,
690 true /* fake entries are read only. */);
691 }
692
693 var rootType;
694 var isReadOnly;
695 var isRootEntry;
696 if (volumeInfo.volumeType === util.VolumeType.DRIVE) {
697 // For Drive, the roots are /root and /other, instead of /. Root URLs
698 // contain trailing slashes.
699 if (entry.fullPath == '/root' || entry.fullPath.indexOf('/root/') === 0) {
700 rootType = RootType.DRIVE;
701 isReadOnly = volumeInfo.isReadOnly;
702 isRootEntry = entry.fullPath === '/root';
703 } else if (entry.fullPath == '/other' ||
704 entry.fullPath.indexOf('/other/') === 0) {
705 rootType = RootType.DRIVE_OTHER;
706 isReadOnly = true;
707 isRootEntry = entry.fullPath === '/other';
708 } else {
709 // Accessing Drive files outside of /drive/root and /drive/other is not
710 // allowed, but can happen. Therefore returning null.
711 return null;
712 }
713 } else {
714 switch (volumeInfo.volumeType) {
715 case util.VolumeType.DOWNLOADS:
716 rootType = RootType.DOWNLOADS;
717 break;
718 case util.VolumeType.REMOVABLE:
719 rootType = RootType.REMOVABLE;
720 break;
721 case util.VolumeType.ARCHIVE:
722 rootType = RootType.ARCHIVE;
723 break;
724 case util.VolumeType.CLOUD_DEVICE:
725 rootType = RootType.CLOUD_DEVICE;
726 break;
727 case util.VolumeType.MTP:
728 rootType = RootType.MTP;
729 break;
730 default:
731 // Programming error, throw an exception.
732 throw new Error('Invalid volume type: ' + volumeInfo.volumeType);
733 }
734 isReadOnly = volumeInfo.isReadOnly;
735 isRootEntry = util.isSameEntry(entry, volumeInfo.fileSystem.root);
736 }
737
738 return new EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly);
739 };
740
741 /**
742 * @param {string} key Key produced by |makeRequestKey_|.
743 * @param {function(VolumeInfo)} successCallback To be called when request
744 * finishes successfully.
745 * @param {function(util.VolumeError)} errorCallback To be called when
746 * request fails.
747 * @private
748 */
749 VolumeManager.prototype.startRequest_ = function(key,
750 successCallback, errorCallback) {
751 if (key in this.requests_) {
752 var request = this.requests_[key];
753 request.successCallbacks.push(successCallback);
754 request.errorCallbacks.push(errorCallback);
755 } else {
756 this.requests_[key] = {
757 successCallbacks: [successCallback],
758 errorCallbacks: [errorCallback],
759
760 timeout: setTimeout(this.onTimeout_.bind(this, key),
761 VolumeManager.TIMEOUT)
762 };
763 }
764 };
765
766 /**
767 * Called if no response received in |TIMEOUT|.
768 * @param {string} key Key produced by |makeRequestKey_|.
769 * @private
770 */
771 VolumeManager.prototype.onTimeout_ = function(key) {
772 this.invokeRequestCallbacks_(this.requests_[key],
773 util.VolumeError.TIMEOUT);
774 delete this.requests_[key];
775 };
776
777 /**
778 * @param {string} key Key produced by |makeRequestKey_|.
779 * @param {util.VolumeError|'success'} status Status received from the API.
780 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
781 * @private
782 */
783 VolumeManager.prototype.finishRequest_ = function(key, status, opt_volumeInfo) {
784 var request = this.requests_[key];
785 if (!request)
786 return;
787
788 clearTimeout(request.timeout);
789 this.invokeRequestCallbacks_(request, status, opt_volumeInfo);
790 delete this.requests_[key];
791 };
792
793 /**
794 * @param {Object} request Structure created in |startRequest_|.
795 * @param {util.VolumeError|string} status If status === 'success'
796 * success callbacks are called.
797 * @param {VolumeInfo=} opt_volumeInfo Volume info of the mounted volume.
798 * @private
799 */
800 VolumeManager.prototype.invokeRequestCallbacks_ = function(
801 request, status, opt_volumeInfo) {
802 var callEach = function(callbacks, self, args) {
803 for (var i = 0; i < callbacks.length; i++) {
804 callbacks[i].apply(self, args);
805 }
806 };
807 if (status === 'success') {
808 callEach(request.successCallbacks, this, [opt_volumeInfo]);
809 } else {
810 volumeManagerUtil.validateError(status);
811 callEach(request.errorCallbacks, this, [status]);
812 }
813 };
814
815 /**
816 * Location information which shows where the path points in FileManager's
817 * file system.
818 *
819 * @param {!VolumeInfo} volumeInfo Volume information.
820 * @param {RootType} rootType Root type.
821 * @param {boolean} isRootEntry Whether the entry is root entry or not.
822 * @param {boolean} isReadOnly Whether the entry is read only or not.
823 * @constructor
824 */
825 function EntryLocation(volumeInfo, rootType, isRootEntry, isReadOnly) {
826 /**
827 * Volume information.
828 * @type {!VolumeInfo}
829 */
830 this.volumeInfo = volumeInfo;
831
832 /**
833 * Root type.
834 * @type {RootType}
835 */
836 this.rootType = rootType;
837
838 /**
839 * Whether the entry is root entry or not.
840 * @type {boolean}
841 */
842 this.isRootEntry = isRootEntry;
843
844 /**
845 * Whether the location obtained from the fake entry correspond to special
846 * searches.
847 * @type {boolean}
848 */
849 this.isSpecialSearchRoot =
850 this.rootType === RootType.DRIVE_OFFLINE ||
851 this.rootType === RootType.DRIVE_SHARED_WITH_ME ||
852 this.rootType === RootType.DRIVE_RECENT;
853
854 /**
855 * Whether the location is under Google Drive or a special search root which
856 * represents a special search from Google Drive.
857 * @type {boolean}
858 */
859 this.isDriveBased =
860 this.rootType === RootType.DRIVE ||
861 this.rootType === RootType.DRIVE_OTHER ||
862 this.rootType === RootType.DRIVE_SHARED_WITH_ME ||
863 this.rootType === RootType.DRIVE_RECENT ||
864 this.rootType === RootType.DRIVE_OFFLINE;
865
866 /**
867 * Whether the given path can be a target path of folder shortcut.
868 * @type {boolean}
869 */
870 this.isEligibleForFolderShortcut =
871 !this.isSpecialSearchRoot &&
872 !this.isRootEntry &&
873 this.isDriveBased;
874
875 /**
876 * Whether the entry is read only or not.
877 * @type {boolean}
878 */
879 this.isReadOnly = isReadOnly;
880
881 Object.freeze(this);
882 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698