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

Side by Side Diff: chrome/browser/resources/file_manager/foreground/js/directory_model.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 // If directory files changes too often, don't rescan directory more than once
8 // per specified interval
9 var SIMULTANEOUS_RESCAN_INTERVAL = 1000;
10 // Used for operations that require almost instant rescan.
11 var SHORT_RESCAN_INTERVAL = 100;
12
13 /**
14 * Data model of the file manager.
15 *
16 * @param {boolean} singleSelection True if only one file could be selected
17 * at the time.
18 * @param {FileFilter} fileFilter Instance of FileFilter.
19 * @param {FileWatcher} fileWatcher Instance of FileWatcher.
20 * @param {MetadataCache} metadataCache The metadata cache service.
21 * @param {VolumeManagerWrapper} volumeManager The volume manager.
22 * @constructor
23 */
24 function DirectoryModel(singleSelection, fileFilter, fileWatcher,
25 metadataCache, volumeManager) {
26 this.fileListSelection_ = singleSelection ?
27 new cr.ui.ListSingleSelectionModel() : new cr.ui.ListSelectionModel();
28
29 this.runningScan_ = null;
30 this.pendingScan_ = null;
31 this.rescanTime_ = null;
32 this.scanFailures_ = 0;
33 this.changeDirectorySequence_ = 0;
34
35 this.fileFilter_ = fileFilter;
36 this.fileFilter_.addEventListener('changed',
37 this.onFilterChanged_.bind(this));
38
39 this.currentFileListContext_ = new FileListContext(
40 fileFilter, metadataCache);
41 this.currentDirContents_ =
42 DirectoryContents.createForDirectory(this.currentFileListContext_, null);
43
44 this.metadataCache_ = metadataCache;
45
46 this.volumeManager_ = volumeManager;
47 this.volumeManager_.volumeInfoList.addEventListener(
48 'splice', this.onVolumeInfoListUpdated_.bind(this));
49
50 this.fileWatcher_ = fileWatcher;
51 this.fileWatcher_.addEventListener(
52 'watcher-directory-changed',
53 this.onWatcherDirectoryChanged_.bind(this));
54 }
55
56 /**
57 * DirectoryModel extends cr.EventTarget.
58 */
59 DirectoryModel.prototype.__proto__ = cr.EventTarget.prototype;
60
61 /**
62 * Disposes the directory model by removing file watchers.
63 */
64 DirectoryModel.prototype.dispose = function() {
65 this.fileWatcher_.dispose();
66 };
67
68 /**
69 * @return {cr.ui.ArrayDataModel} Files in the current directory.
70 */
71 DirectoryModel.prototype.getFileList = function() {
72 return this.currentFileListContext_.fileList;
73 };
74
75 /**
76 * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection
77 * in the fileList.
78 */
79 DirectoryModel.prototype.getFileListSelection = function() {
80 return this.fileListSelection_;
81 };
82
83 /**
84 * @return {?RootType} Root type of current root, or null if not found.
85 */
86 DirectoryModel.prototype.getCurrentRootType = function() {
87 var entry = this.currentDirContents_.getDirectoryEntry();
88 if (!entry)
89 return null;
90
91 var locationInfo = this.volumeManager_.getLocationInfo(entry);
92 if (!locationInfo)
93 return null;
94
95 return locationInfo.rootType;
96 };
97
98 /**
99 * @return {boolean} True if the current directory is read only. If there is
100 * no entry set, then returns true.
101 */
102 DirectoryModel.prototype.isReadOnly = function() {
103 var currentDirEntry = this.getCurrentDirEntry();
104 if (currentDirEntry) {
105 var locationInfo = this.volumeManager_.getLocationInfo(currentDirEntry);
106 if (locationInfo)
107 return locationInfo.isReadOnly;
108 }
109 return true;
110 };
111
112 /**
113 * @return {boolean} True if the a scan is active.
114 */
115 DirectoryModel.prototype.isScanning = function() {
116 return this.currentDirContents_.isScanning();
117 };
118
119 /**
120 * @return {boolean} True if search is in progress.
121 */
122 DirectoryModel.prototype.isSearching = function() {
123 return this.currentDirContents_.isSearch();
124 };
125
126 /**
127 * Updates the selection by using the updateFunc and publish the change event.
128 * If updateFunc returns true, it force to dispatch the change event even if the
129 * selection index is not changed.
130 *
131 * @param {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} selection
132 * Selection to be updated.
133 * @param {function(): boolean} updateFunc Function updating the selection.
134 * @private
135 */
136 DirectoryModel.prototype.updateSelectionAndPublishEvent_ =
137 function(selection, updateFunc) {
138 // Begin change.
139 selection.beginChange();
140
141 // If dispatchNeeded is true, we should ensure the change event is
142 // dispatched.
143 var dispatchNeeded = updateFunc();
144
145 // Check if the change event is dispatched in the endChange function
146 // or not.
147 var eventDispatched = function() { dispatchNeeded = false; };
148 selection.addEventListener('change', eventDispatched);
149 selection.endChange();
150 selection.removeEventListener('change', eventDispatched);
151
152 // If the change event have been already dispatched, dispatchNeeded is false.
153 if (dispatchNeeded) {
154 var event = new Event('change');
155 // The selection status (selected or not) is not changed because
156 // this event is caused by the change of selected item.
157 event.changes = [];
158 selection.dispatchEvent(event);
159 }
160 };
161
162 /**
163 * Invoked when a change in the directory is detected by the watcher.
164 * @private
165 */
166 DirectoryModel.prototype.onWatcherDirectoryChanged_ = function() {
167 this.rescanSoon();
168 };
169
170 /**
171 * Invoked when filters are changed.
172 * @private
173 */
174 DirectoryModel.prototype.onFilterChanged_ = function() {
175 this.rescanSoon();
176 };
177
178 /**
179 * Returns the filter.
180 * @return {FileFilter} The file filter.
181 */
182 DirectoryModel.prototype.getFileFilter = function() {
183 return this.fileFilter_;
184 };
185
186 /**
187 * @return {DirectoryEntry} Current directory.
188 */
189 DirectoryModel.prototype.getCurrentDirEntry = function() {
190 return this.currentDirContents_.getDirectoryEntry();
191 };
192
193 /**
194 * @return {Array.<Entry>} Array of selected entries.
195 * @private
196 */
197 DirectoryModel.prototype.getSelectedEntries_ = function() {
198 var indexes = this.fileListSelection_.selectedIndexes;
199 var fileList = this.getFileList();
200 if (fileList) {
201 return indexes.map(function(i) {
202 return fileList.item(i);
203 });
204 }
205 return [];
206 };
207
208 /**
209 * @param {Array.<Entry>} value List of selected entries.
210 * @private
211 */
212 DirectoryModel.prototype.setSelectedEntries_ = function(value) {
213 var indexes = [];
214 var fileList = this.getFileList();
215 var urls = util.entriesToURLs(value);
216
217 for (var i = 0; i < fileList.length; i++) {
218 if (urls.indexOf(fileList.item(i).toURL()) !== -1)
219 indexes.push(i);
220 }
221 this.fileListSelection_.selectedIndexes = indexes;
222 };
223
224 /**
225 * @return {Entry} Lead entry.
226 * @private
227 */
228 DirectoryModel.prototype.getLeadEntry_ = function() {
229 var index = this.fileListSelection_.leadIndex;
230 return index >= 0 && this.getFileList().item(index);
231 };
232
233 /**
234 * @param {Entry} value The new lead entry.
235 * @private
236 */
237 DirectoryModel.prototype.setLeadEntry_ = function(value) {
238 var fileList = this.getFileList();
239 for (var i = 0; i < fileList.length; i++) {
240 if (util.isSameEntry(fileList.item(i), value)) {
241 this.fileListSelection_.leadIndex = i;
242 return;
243 }
244 }
245 };
246
247 /**
248 * Schedule rescan with short delay.
249 */
250 DirectoryModel.prototype.rescanSoon = function() {
251 this.scheduleRescan(SHORT_RESCAN_INTERVAL);
252 };
253
254 /**
255 * Schedule rescan with delay. Designed to handle directory change
256 * notification.
257 */
258 DirectoryModel.prototype.rescanLater = function() {
259 this.scheduleRescan(SIMULTANEOUS_RESCAN_INTERVAL);
260 };
261
262 /**
263 * Schedule rescan with delay. If another rescan has been scheduled does
264 * nothing. File operation may cause a few notifications what should cause
265 * a single refresh.
266 * @param {number} delay Delay in ms after which the rescan will be performed.
267 */
268 DirectoryModel.prototype.scheduleRescan = function(delay) {
269 if (this.rescanTime_) {
270 if (this.rescanTime_ <= Date.now() + delay)
271 return;
272 clearTimeout(this.rescanTimeoutId_);
273 }
274
275 this.rescanTime_ = Date.now() + delay;
276 this.rescanTimeoutId_ = setTimeout(this.rescan.bind(this), delay);
277 };
278
279 /**
280 * Cancel a rescan on timeout if it is scheduled.
281 * @private
282 */
283 DirectoryModel.prototype.clearRescanTimeout_ = function() {
284 this.rescanTime_ = null;
285 if (this.rescanTimeoutId_) {
286 clearTimeout(this.rescanTimeoutId_);
287 this.rescanTimeoutId_ = null;
288 }
289 };
290
291 /**
292 * Rescan current directory. May be called indirectly through rescanLater or
293 * directly in order to reflect user action. Will first cache all the directory
294 * contents in an array, then seamlessly substitute the fileList contents,
295 * preserving the select element etc.
296 *
297 * This should be to scan the contents of current directory (or search).
298 */
299 DirectoryModel.prototype.rescan = function() {
300 this.clearRescanTimeout_();
301 if (this.runningScan_) {
302 this.pendingRescan_ = true;
303 return;
304 }
305
306 var dirContents = this.currentDirContents_.clone();
307 dirContents.setFileList([]);
308
309 var successCallback = (function() {
310 this.replaceDirectoryContents_(dirContents);
311 cr.dispatchSimpleEvent(this, 'rescan-completed');
312 }).bind(this);
313
314 this.scan_(dirContents,
315 successCallback, function() {}, function() {}, function() {});
316 };
317
318 /**
319 * Run scan on the current DirectoryContents. The active fileList is cleared and
320 * the entries are added directly.
321 *
322 * This should be used when changing directory or initiating a new search.
323 *
324 * @param {DirectoryContentes} newDirContents New DirectoryContents instance to
325 * replace currentDirContents_.
326 * @param {function()=} opt_callback Called on success.
327 * @private
328 */
329 DirectoryModel.prototype.clearAndScan_ = function(newDirContents,
330 opt_callback) {
331 if (this.currentDirContents_.isScanning())
332 this.currentDirContents_.cancelScan();
333 this.currentDirContents_ = newDirContents;
334 this.clearRescanTimeout_();
335
336 if (this.pendingScan_)
337 this.pendingScan_ = false;
338
339 if (this.runningScan_) {
340 if (this.runningScan_.isScanning())
341 this.runningScan_.cancelScan();
342 this.runningScan_ = null;
343 }
344
345 var onDone = function() {
346 cr.dispatchSimpleEvent(this, 'scan-completed');
347 if (opt_callback)
348 opt_callback();
349 }.bind(this);
350
351 var onFailed = function() {
352 cr.dispatchSimpleEvent(this, 'scan-failed');
353 }.bind(this);
354
355 var onUpdated = function() {
356 cr.dispatchSimpleEvent(this, 'scan-updated');
357 }.bind(this);
358
359 var onCancelled = function() {
360 cr.dispatchSimpleEvent(this, 'scan-cancelled');
361 }.bind(this);
362
363 // Clear the table, and start scanning.
364 cr.dispatchSimpleEvent(this, 'scan-started');
365 var fileList = this.getFileList();
366 fileList.splice(0, fileList.length);
367 this.scan_(this.currentDirContents_,
368 onDone, onFailed, onUpdated, onCancelled);
369 };
370
371 /**
372 * Perform a directory contents scan. Should be called only from rescan() and
373 * clearAndScan_().
374 *
375 * @param {DirectoryContents} dirContents DirectoryContents instance on which
376 * the scan will be run.
377 * @param {function()} successCallback Callback on success.
378 * @param {function()} failureCallback Callback on failure.
379 * @param {function()} updatedCallback Callback on update. Only on the last
380 * update, {@code successCallback} is called instead of this.
381 * @param {function()} cancelledCallback Callback on cancel.
382 * @private
383 */
384 DirectoryModel.prototype.scan_ = function(
385 dirContents,
386 successCallback, failureCallback, updatedCallback, cancelledCallback) {
387 var self = this;
388
389 /**
390 * Runs pending scan if there is one.
391 *
392 * @return {boolean} Did pending scan exist.
393 */
394 var maybeRunPendingRescan = function() {
395 if (this.pendingRescan_) {
396 this.rescanSoon();
397 this.pendingRescan_ = false;
398 return true;
399 }
400 return false;
401 }.bind(this);
402
403 var onSuccess = function() {
404 // Record metric for Downloads directory.
405 if (!dirContents.isSearch()) {
406 var locationInfo =
407 this.volumeManager_.getLocationInfo(dirContents.getDirectoryEntry());
408 if (locationInfo.volumeInfo.volumeType === util.VolumeType.DOWNLOADS &&
409 locationInfo.isRootEntry) {
410 metrics.recordMediumCount('DownloadsCount',
411 dirContents.fileList_.length);
412 }
413 }
414
415 this.runningScan_ = null;
416 successCallback();
417 this.scanFailures_ = 0;
418 maybeRunPendingRescan();
419 }.bind(this);
420
421 var onFailure = function() {
422 this.runningScan_ = null;
423 this.scanFailures_++;
424 failureCallback();
425
426 if (maybeRunPendingRescan())
427 return;
428
429 if (this.scanFailures_ <= 1)
430 this.rescanLater();
431 }.bind(this);
432
433 this.runningScan_ = dirContents;
434
435 dirContents.addEventListener('scan-completed', onSuccess);
436 dirContents.addEventListener('scan-updated', updatedCallback);
437 dirContents.addEventListener('scan-failed', onFailure);
438 dirContents.addEventListener('scan-cancelled', cancelledCallback);
439 dirContents.scan();
440 };
441
442 /**
443 * @param {DirectoryContents} dirContents DirectoryContents instance.
444 * @private
445 */
446 DirectoryModel.prototype.replaceDirectoryContents_ = function(dirContents) {
447 cr.dispatchSimpleEvent(this, 'begin-update-files');
448 this.updateSelectionAndPublishEvent_(this.fileListSelection_, function() {
449 var selectedEntries = this.getSelectedEntries_();
450 var selectedIndices = this.fileListSelection_.selectedIndexes;
451
452 // Restore leadIndex in case leadName no longer exists.
453 var leadIndex = this.fileListSelection_.leadIndex;
454 var leadEntry = this.getLeadEntry_();
455
456 this.currentDirContents_ = dirContents;
457 dirContents.replaceContextFileList();
458
459 this.setSelectedEntries_(selectedEntries);
460 this.fileListSelection_.leadIndex = leadIndex;
461 this.setLeadEntry_(leadEntry);
462
463 // If nothing is selected after update, then select file next to the
464 // latest selection
465 var forceChangeEvent = false;
466 if (this.fileListSelection_.selectedIndexes.length == 0 &&
467 selectedIndices.length != 0) {
468 var maxIdx = Math.max.apply(null, selectedIndices);
469 this.selectIndex(Math.min(maxIdx - selectedIndices.length + 2,
470 this.getFileList().length) - 1);
471 forceChangeEvent = true;
472 }
473 return forceChangeEvent;
474 }.bind(this));
475
476 cr.dispatchSimpleEvent(this, 'end-update-files');
477 };
478
479 /**
480 * Callback when an entry is changed.
481 * @param {util.EntryChangedKind} kind How the entry is changed.
482 * @param {Entry} entry The changed entry.
483 */
484 DirectoryModel.prototype.onEntryChanged = function(kind, entry) {
485 // TODO(hidehiko): We should update directory model even the search result
486 // is shown.
487 var rootType = this.getCurrentRootType();
488 if ((rootType === RootType.DRIVE ||
489 rootType === RootType.DRIVE_SHARED_WITH_ME ||
490 rootType === RootType.DRIVE_RECENT ||
491 rootType === RootType.DRIVE_OFFLINE) &&
492 this.isSearching())
493 return;
494
495 if (kind == util.EntryChangedKind.CREATED) {
496 // Refresh the cache.
497 this.metadataCache_.clear([entry], '*');
498 entry.getParent(function(parentEntry) {
499 if (!util.isSameEntry(this.getCurrentDirEntry(), parentEntry)) {
500 // Do nothing if current directory changed during async operations.
501 return;
502 }
503 this.currentDirContents_.prefetchMetadata([entry], function() {
504 if (!util.isSameEntry(this.getCurrentDirEntry(), parentEntry)) {
505 // Do nothing if current directory changed during async operations.
506 return;
507 }
508
509 var index = this.findIndexByEntry_(entry);
510 if (index >= 0)
511 this.getFileList().replaceItem(this.getFileList().item(index), entry);
512 else
513 this.getFileList().push(entry);
514 }.bind(this));
515 }.bind(this));
516 } else {
517 // This is the delete event.
518 var index = this.findIndexByEntry_(entry);
519 if (index >= 0)
520 this.getFileList().splice(index, 1);
521 }
522 };
523
524 /**
525 * @param {Entry} entry The entry to be searched.
526 * @return {number} The index in the fileList, or -1 if not found.
527 * @private
528 */
529 DirectoryModel.prototype.findIndexByEntry_ = function(entry) {
530 var fileList = this.getFileList();
531 for (var i = 0; i < fileList.length; i++) {
532 if (util.isSameEntry(fileList.item(i), entry))
533 return i;
534 }
535 return -1;
536 };
537
538 /**
539 * Called when rename is done successfully.
540 * Note: conceptually, DirectoryModel should work without this, because entries
541 * can be renamed by other systems anytime and Files.app should reflect it
542 * correctly.
543 * TODO(hidehiko): investigate more background, and remove this if possible.
544 *
545 * @param {Entry} oldEntry The old entry.
546 * @param {Entry} newEntry The new entry.
547 * @param {function()} opt_callback Called on completion.
548 */
549 DirectoryModel.prototype.onRenameEntry = function(
550 oldEntry, newEntry, opt_callback) {
551 this.currentDirContents_.prefetchMetadata([newEntry], function() {
552 // If the current directory is the old entry, then quietly change to the
553 // new one.
554 if (util.isSameEntry(oldEntry, this.getCurrentDirEntry()))
555 this.changeDirectoryEntry(newEntry);
556
557 // Replace the old item with the new item.
558 // If the entry doesn't exist in the list, it has been updated from
559 // outside (probably by directory rescan) and is just ignored.
560 this.getFileList().replaceItem(oldEntry, newEntry);
561
562 // Run callback, finally.
563 if (opt_callback)
564 opt_callback();
565 }.bind(this));
566 };
567
568 /**
569 * Creates directory and updates the file list.
570 *
571 * @param {string} name Directory name.
572 * @param {function(DirectoryEntry)} successCallback Callback on success.
573 * @param {function(FileError)} errorCallback Callback on failure.
574 */
575 DirectoryModel.prototype.createDirectory = function(name,
576 successCallback,
577 errorCallback) {
578 // Obtain and check the current directory.
579 var entry = this.getCurrentDirEntry();
580 if (!entry || this.isSearching()) {
581 errorCallback(util.createDOMError(
582 util.FileError.INVALID_MODIFICATION_ERR));
583 return;
584 }
585
586 var tracker = this.createDirectoryChangeTracker();
587 tracker.start();
588
589 new Promise(entry.getDirectory.bind(
590 entry, name, {create: true, exclusive: true})).
591
592 then(function(newEntry) {
593 // Refresh the cache.
594 this.metadataCache_.clear([newEntry], '*');
595 return new Promise(function(onFulfilled, onRejected) {
596 this.metadataCache_.get([newEntry],
597 'filesystem',
598 onFulfilled.bind(null, newEntry));
599 }.bind(this));
600 }.bind(this)).
601
602 then(function(newEntry) {
603 // Do not change anything or call the callback if current
604 // directory changed.
605 tracker.stop();
606 if (tracker.hasChanged)
607 return;
608
609 // If target directory is already in the list, just select it.
610 var existing = this.getFileList().slice().filter(
611 function(e) { return e.name === name; });
612 if (existing.length) {
613 this.selectEntry(newEntry);
614 successCallback(existing[0]);
615 } else {
616 this.fileListSelection_.beginChange();
617 this.getFileList().splice(0, 0, newEntry);
618 this.selectEntry(newEntry);
619 this.fileListSelection_.endChange();
620 successCallback(newEntry);
621 }
622 }.bind(this), function(reason) {
623 tracker.stop();
624 errorCallback(reason);
625 });
626 };
627
628 /**
629 * Change the current directory to the directory represented by
630 * a DirectoryEntry or a fake entry.
631 *
632 * Dispatches the 'directory-changed' event when the directory is successfully
633 * changed.
634 *
635 * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to
636 * be opened.
637 * @param {function()=} opt_callback Executed if the directory loads
638 * successfully.
639 */
640 DirectoryModel.prototype.changeDirectoryEntry = function(
641 dirEntry, opt_callback) {
642 // Increment the sequence value.
643 this.changeDirectorySequence_++;
644 this.clearSearch_();
645
646 var promise = new Promise(
647 function(onFulfilled, onRejected) {
648 this.fileWatcher_.changeWatchedDirectory(dirEntry, onFulfilled);
649 }.bind(this)).
650
651 then(function(sequence) {
652 return new Promise(function(onFulfilled, onRejected) {
653 if (this.changeDirectorySequence_ !== sequence)
654 return;
655
656 var newDirectoryContents = this.createDirectoryContents_(
657 this.currentFileListContext_, dirEntry, '');
658 if (!newDirectoryContents)
659 return;
660
661 var previousDirEntry = this.currentDirContents_.getDirectoryEntry();
662 this.clearAndScan_(newDirectoryContents, opt_callback);
663
664 // For tests that open the dialog to empty directories, everything is
665 // loaded at this point.
666 util.testSendMessage('directory-change-complete');
667
668 var event = new Event('directory-changed');
669 event.previousDirEntry = previousDirEntry;
670 event.newDirEntry = dirEntry;
671 this.dispatchEvent(event);
672 }.bind(this));
673 }.bind(this, this.changeDirectorySequence_));
674 };
675
676 /**
677 * Clears the selection in the file list.
678 */
679 DirectoryModel.prototype.clearSelection = function() {
680 this.setSelectedEntries_([]);
681 };
682
683 /**
684 * Creates an object which could say whether directory has changed while it has
685 * been active or not. Designed for long operations that should be cancelled
686 * if the used change current directory.
687 * @return {Object} Created object.
688 */
689 DirectoryModel.prototype.createDirectoryChangeTracker = function() {
690 var tracker = {
691 dm_: this,
692 active_: false,
693 hasChanged: false,
694
695 start: function() {
696 if (!this.active_) {
697 this.dm_.addEventListener('directory-changed',
698 this.onDirectoryChange_);
699 this.active_ = true;
700 this.hasChanged = false;
701 }
702 },
703
704 stop: function() {
705 if (this.active_) {
706 this.dm_.removeEventListener('directory-changed',
707 this.onDirectoryChange_);
708 this.active_ = false;
709 }
710 },
711
712 onDirectoryChange_: function(event) {
713 tracker.stop();
714 tracker.hasChanged = true;
715 }
716 };
717 return tracker;
718 };
719
720 /**
721 * @param {Entry} entry Entry to be selected.
722 */
723 DirectoryModel.prototype.selectEntry = function(entry) {
724 var fileList = this.getFileList();
725 for (var i = 0; i < fileList.length; i++) {
726 if (fileList.item(i).toURL() === entry.toURL()) {
727 this.selectIndex(i);
728 return;
729 }
730 }
731 };
732
733 /**
734 * @param {Array.<string>} entries Array of entries.
735 */
736 DirectoryModel.prototype.selectEntries = function(entries) {
737 // URLs are needed here, since we are comparing Entries by URLs.
738 var urls = util.entriesToURLs(entries);
739 var fileList = this.getFileList();
740 this.fileListSelection_.beginChange();
741 this.fileListSelection_.unselectAll();
742 for (var i = 0; i < fileList.length; i++) {
743 if (urls.indexOf(fileList.item(i).toURL()) >= 0)
744 this.fileListSelection_.setIndexSelected(i, true);
745 }
746 this.fileListSelection_.endChange();
747 };
748
749 /**
750 * @param {number} index Index of file.
751 */
752 DirectoryModel.prototype.selectIndex = function(index) {
753 // this.focusCurrentList_();
754 if (index >= this.getFileList().length)
755 return;
756
757 // If a list bound with the model it will do scrollIndexIntoView(index).
758 this.fileListSelection_.selectedIndex = index;
759 };
760
761 /**
762 * Handles update of VolumeInfoList.
763 * @param {Event} event Event of VolumeInfoList's 'splice'.
764 * @private
765 */
766 DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) {
767 // When the volume where we are is unmounted, fallback to the default volume's
768 // root. If current directory path is empty, stop the fallback
769 // since the current directory is initializing now.
770 if (this.getCurrentDirEntry() &&
771 !this.volumeManager_.getVolumeInfo(this.getCurrentDirEntry())) {
772 this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) {
773 this.changeDirectoryEntry(displayRoot);
774 }.bind(this));
775 }
776 };
777
778 /**
779 * Creates directory contents for the entry and query.
780 *
781 * @param {FileListContext} context File list context.
782 * @param {DirectoryEntry} entry Current directory.
783 * @param {string=} opt_query Search query string.
784 * @return {DirectoryContents} Directory contents.
785 * @private
786 */
787 DirectoryModel.prototype.createDirectoryContents_ =
788 function(context, entry, opt_query) {
789 var query = (opt_query || '').trimLeft();
790 var locationInfo = this.volumeManager_.getLocationInfo(entry);
791 if (!locationInfo)
792 return null;
793 var canUseDriveSearch = this.volumeManager_.getDriveConnectionState().type !==
794 util.DriveConnectionType.OFFLINE &&
795 locationInfo.isDriveBased;
796
797 if (query && canUseDriveSearch) {
798 // Drive search.
799 return DirectoryContents.createForDriveSearch(context, entry, query);
800 } else if (query) {
801 // Local search.
802 return DirectoryContents.createForLocalSearch(context, entry, query);
803 } if (locationInfo.isSpecialSearchRoot) {
804 // Drive special search.
805 var searchType;
806 switch (locationInfo.rootType) {
807 case RootType.DRIVE_OFFLINE:
808 searchType =
809 DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE;
810 break;
811 case RootType.DRIVE_SHARED_WITH_ME:
812 searchType =
813 DriveMetadataSearchContentScanner.SearchType.SEARCH_SHARED_WITH_ME;
814 break;
815 case RootType.DRIVE_RECENT:
816 searchType =
817 DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES;
818 break;
819 default:
820 // Unknown special search entry.
821 throw new Error('Unknown special search type.');
822 }
823 return DirectoryContents.createForDriveMetadataSearch(
824 context,
825 entry,
826 searchType);
827 } else {
828 // Local fetch or search.
829 return DirectoryContents.createForDirectory(context, entry);
830 }
831 };
832
833 /**
834 * Performs search and displays results. The search type is dependent on the
835 * current directory. If we are currently on drive, server side content search
836 * over drive mount point. If the current directory is not on the drive, file
837 * name search over current directory will be performed.
838 *
839 * @param {string} query Query that will be searched for.
840 * @param {function(Event)} onSearchRescan Function that will be called when the
841 * search directory is rescanned (i.e. search results are displayed).
842 * @param {function()} onClearSearch Function to be called when search state
843 * gets cleared.
844 * TODO(olege): Change callbacks to events.
845 */
846 DirectoryModel.prototype.search = function(query,
847 onSearchRescan,
848 onClearSearch) {
849 this.clearSearch_();
850 var currentDirEntry = this.getCurrentDirEntry();
851 if (!currentDirEntry) {
852 // Not yet initialized. Do nothing.
853 return;
854 }
855
856 if (!(query || '').trimLeft()) {
857 if (this.isSearching()) {
858 var newDirContents = this.createDirectoryContents_(
859 this.currentFileListContext_,
860 currentDirEntry);
861 this.clearAndScan_(newDirContents);
862 }
863 return;
864 }
865
866 var newDirContents = this.createDirectoryContents_(
867 this.currentFileListContext_, currentDirEntry, query);
868 if (!newDirContents)
869 return;
870
871 this.onSearchCompleted_ = onSearchRescan;
872 this.onClearSearch_ = onClearSearch;
873 this.addEventListener('scan-completed', this.onSearchCompleted_);
874 this.clearAndScan_(newDirContents);
875 };
876
877 /**
878 * In case the search was active, remove listeners and send notifications on
879 * its canceling.
880 * @private
881 */
882 DirectoryModel.prototype.clearSearch_ = function() {
883 if (!this.isSearching())
884 return;
885
886 if (this.onSearchCompleted_) {
887 this.removeEventListener('scan-completed', this.onSearchCompleted_);
888 this.onSearchCompleted_ = null;
889 }
890
891 if (this.onClearSearch_) {
892 this.onClearSearch_();
893 this.onClearSearch_ = null;
894 }
895 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698