Index: ui/file_manager/file_manager/foreground/js/directory_model.js |
diff --git a/ui/file_manager/file_manager/foreground/js/directory_model.js b/ui/file_manager/file_manager/foreground/js/directory_model.js |
deleted file mode 100644 |
index 1f9435318a59f7099650efb829bb2981c76d37c1..0000000000000000000000000000000000000000 |
--- a/ui/file_manager/file_manager/foreground/js/directory_model.js |
+++ /dev/null |
@@ -1,1135 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-'use strict'; |
- |
-// If directory files changes too often, don't rescan directory more than once |
-// per specified interval |
-var SIMULTANEOUS_RESCAN_INTERVAL = 500; |
-// Used for operations that require almost instant rescan. |
-var SHORT_RESCAN_INTERVAL = 100; |
- |
-/** |
- * Data model of the file manager. |
- * |
- * @param {boolean} singleSelection True if only one file could be selected |
- * at the time. |
- * @param {FileFilter} fileFilter Instance of FileFilter. |
- * @param {FileWatcher} fileWatcher Instance of FileWatcher. |
- * @param {MetadataCache} metadataCache The metadata cache service. |
- * @param {VolumeManagerWrapper} volumeManager The volume manager. |
- * @constructor |
- * @extends {cr.EventTarget} |
- */ |
-function DirectoryModel(singleSelection, fileFilter, fileWatcher, |
- metadataCache, volumeManager) { |
- this.fileListSelection_ = singleSelection ? |
- new cr.ui.ListSingleSelectionModel() : new cr.ui.ListSelectionModel(); |
- |
- this.runningScan_ = null; |
- this.pendingScan_ = null; |
- this.rescanTime_ = null; |
- this.scanFailures_ = 0; |
- this.changeDirectorySequence_ = 0; |
- |
- this.directoryChangeQueue_ = new AsyncUtil.Queue(); |
- this.rescanAggregator_ = new AsyncUtil.Aggregator( |
- this.rescanSoon.bind(this, true), 500); |
- |
- this.fileFilter_ = fileFilter; |
- this.fileFilter_.addEventListener('changed', |
- this.onFilterChanged_.bind(this)); |
- |
- this.currentFileListContext_ = new FileListContext( |
- fileFilter, metadataCache); |
- this.currentDirContents_ = |
- DirectoryContents.createForDirectory(this.currentFileListContext_, null); |
- |
- this.metadataCache_ = metadataCache; |
- |
- this.volumeManager_ = volumeManager; |
- this.volumeManager_.volumeInfoList.addEventListener( |
- 'splice', this.onVolumeInfoListUpdated_.bind(this)); |
- |
- this.fileWatcher_ = fileWatcher; |
- this.fileWatcher_.addEventListener( |
- 'watcher-directory-changed', |
- this.onWatcherDirectoryChanged_.bind(this)); |
-} |
- |
-/** |
- * DirectoryModel extends cr.EventTarget. |
- */ |
-DirectoryModel.prototype.__proto__ = cr.EventTarget.prototype; |
- |
-/** |
- * Disposes the directory model by removing file watchers. |
- */ |
-DirectoryModel.prototype.dispose = function() { |
- this.fileWatcher_.dispose(); |
-}; |
- |
-/** |
- * @return {cr.ui.ArrayDataModel} Files in the current directory. |
- */ |
-DirectoryModel.prototype.getFileList = function() { |
- return this.currentFileListContext_.fileList; |
-}; |
- |
-/** |
- * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection |
- * in the fileList. |
- */ |
-DirectoryModel.prototype.getFileListSelection = function() { |
- return this.fileListSelection_; |
-}; |
- |
-/** |
- * @return {?VolumeManagerCommon.RootType} Root type of current root, or null if |
- * not found. |
- */ |
-DirectoryModel.prototype.getCurrentRootType = function() { |
- var entry = this.currentDirContents_.getDirectoryEntry(); |
- if (!entry) |
- return null; |
- |
- var locationInfo = this.volumeManager_.getLocationInfo(entry); |
- if (!locationInfo) |
- return null; |
- |
- return locationInfo.rootType; |
-}; |
- |
-/** |
- * @return {boolean} True if the current directory is read only. If there is |
- * no entry set, then returns true. |
- */ |
-DirectoryModel.prototype.isReadOnly = function() { |
- var currentDirEntry = this.getCurrentDirEntry(); |
- if (currentDirEntry) { |
- var locationInfo = this.volumeManager_.getLocationInfo(currentDirEntry); |
- if (locationInfo) |
- return locationInfo.isReadOnly; |
- } |
- return true; |
-}; |
- |
-/** |
- * @return {boolean} True if the a scan is active. |
- */ |
-DirectoryModel.prototype.isScanning = function() { |
- return this.currentDirContents_.isScanning(); |
-}; |
- |
-/** |
- * @return {boolean} True if search is in progress. |
- */ |
-DirectoryModel.prototype.isSearching = function() { |
- return this.currentDirContents_.isSearch(); |
-}; |
- |
-/** |
- * Updates the selection by using the updateFunc and publish the change event. |
- * If updateFunc returns true, it force to dispatch the change event even if the |
- * selection index is not changed. |
- * |
- * @param {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} selection |
- * Selection to be updated. |
- * @param {function(): boolean} updateFunc Function updating the selection. |
- * @private |
- */ |
-DirectoryModel.prototype.updateSelectionAndPublishEvent_ = |
- function(selection, updateFunc) { |
- // Begin change. |
- selection.beginChange(); |
- |
- // If dispatchNeeded is true, we should ensure the change event is |
- // dispatched. |
- var dispatchNeeded = updateFunc(); |
- |
- // Check if the change event is dispatched in the endChange function |
- // or not. |
- var eventDispatched = function() { dispatchNeeded = false; }; |
- selection.addEventListener('change', eventDispatched); |
- selection.endChange(); |
- selection.removeEventListener('change', eventDispatched); |
- |
- // If the change event have been already dispatched, dispatchNeeded is false. |
- if (dispatchNeeded) { |
- var event = new Event('change'); |
- // The selection status (selected or not) is not changed because |
- // this event is caused by the change of selected item. |
- event.changes = []; |
- selection.dispatchEvent(event); |
- } |
-}; |
- |
-/** |
- * Invoked when a change in the directory is detected by the watcher. |
- * @param {Event} event Event object. |
- * @private |
- */ |
-DirectoryModel.prototype.onWatcherDirectoryChanged_ = function(event) { |
- var directoryEntry = this.getCurrentDirEntry(); |
- |
- // If the change is deletion of currentDir, move up to its parent directory. |
- directoryEntry.getDirectory(directoryEntry.fullPath, {create: false}, |
- function() {}, |
- function() { |
- var volumeInfo = this.volumeManager_.getVolumeInfo(directoryEntry); |
- if (volumeInfo) { |
- volumeInfo.resolveDisplayRoot().then(function(displayRoot) { |
- this.changeDirectoryEntry(displayRoot); |
- }.bind(this)); |
- } |
- }.bind(this)); |
- |
- if (event.changedFiles) { |
- var addedOrUpdatedFileUrls = []; |
- var deletedFileUrls = []; |
- event.changedFiles.forEach(function(change) { |
- if (change.changes.length === 1 && change.changes[0] === 'delete') |
- deletedFileUrls.push(change.url); |
- else |
- addedOrUpdatedFileUrls.push(change.url); |
- }); |
- |
- util.URLsToEntries(addedOrUpdatedFileUrls).then(function(result) { |
- deletedFileUrls = deletedFileUrls.concat(result.failureUrls); |
- |
- // Passing the resolved entries and failed URLs as the removed files. |
- // The URLs are removed files and they chan't be resolved. |
- this.partialUpdate_(result.entries, deletedFileUrls); |
- }.bind(this)).catch(function(error) { |
- console.error('Error in proceeding the changed event.', error, |
- 'Fallback to force-refresh'); |
- this.rescanAggregator_.run(); |
- }.bind(this)); |
- } else { |
- // Invokes force refresh if the detailed information isn't provided. |
- // This can occur very frequently (e.g. when copying files into Downlaods) |
- // and rescan is heavy operation, so we keep some interval for each rescan. |
- this.rescanAggregator_.run(); |
- } |
-}; |
- |
-/** |
- * Invoked when filters are changed. |
- * @private |
- */ |
-DirectoryModel.prototype.onFilterChanged_ = function() { |
- this.rescanSoon(false); |
-}; |
- |
-/** |
- * Returns the filter. |
- * @return {FileFilter} The file filter. |
- */ |
-DirectoryModel.prototype.getFileFilter = function() { |
- return this.fileFilter_; |
-}; |
- |
-/** |
- * @return {DirectoryEntry} Current directory. |
- */ |
-DirectoryModel.prototype.getCurrentDirEntry = function() { |
- return this.currentDirContents_.getDirectoryEntry(); |
-}; |
- |
-/** |
- * @return {Array.<Entry>} Array of selected entries. |
- * @private |
- */ |
-DirectoryModel.prototype.getSelectedEntries_ = function() { |
- var indexes = this.fileListSelection_.selectedIndexes; |
- var fileList = this.getFileList(); |
- if (fileList) { |
- return indexes.map(function(i) { |
- return fileList.item(i); |
- }); |
- } |
- return []; |
-}; |
- |
-/** |
- * @param {Array.<Entry>} value List of selected entries. |
- * @private |
- */ |
-DirectoryModel.prototype.setSelectedEntries_ = function(value) { |
- var indexes = []; |
- var fileList = this.getFileList(); |
- var urls = util.entriesToURLs(value); |
- |
- for (var i = 0; i < fileList.length; i++) { |
- if (urls.indexOf(fileList.item(i).toURL()) !== -1) |
- indexes.push(i); |
- } |
- this.fileListSelection_.selectedIndexes = indexes; |
-}; |
- |
-/** |
- * @return {Entry} Lead entry. |
- * @private |
- */ |
-DirectoryModel.prototype.getLeadEntry_ = function() { |
- var index = this.fileListSelection_.leadIndex; |
- return index >= 0 && this.getFileList().item(index); |
-}; |
- |
-/** |
- * @param {Entry} value The new lead entry. |
- * @private |
- */ |
-DirectoryModel.prototype.setLeadEntry_ = function(value) { |
- var fileList = this.getFileList(); |
- for (var i = 0; i < fileList.length; i++) { |
- if (util.isSameEntry(/** @type {Entry} */ (fileList.item(i)), value)) { |
- this.fileListSelection_.leadIndex = i; |
- return; |
- } |
- } |
-}; |
- |
-/** |
- * Schedule rescan with short delay. |
- * @param {boolean} refresh True to refresh metadata, or false to use cached |
- * one. |
- */ |
-DirectoryModel.prototype.rescanSoon = function(refresh) { |
- this.scheduleRescan(SHORT_RESCAN_INTERVAL, refresh); |
-}; |
- |
-/** |
- * Schedule rescan with delay. Designed to handle directory change |
- * notification. |
- * @param {boolean} refresh True to refresh metadata, or false to use cached |
- * one. |
- */ |
-DirectoryModel.prototype.rescanLater = function(refresh) { |
- this.scheduleRescan(SIMULTANEOUS_RESCAN_INTERVAL, refresh); |
-}; |
- |
-/** |
- * Schedule rescan with delay. If another rescan has been scheduled does |
- * nothing. File operation may cause a few notifications what should cause |
- * a single refresh. |
- * @param {number} delay Delay in ms after which the rescan will be performed. |
- * @param {boolean} refresh True to refresh metadata, or false to use cached |
- * one. |
- */ |
-DirectoryModel.prototype.scheduleRescan = function(delay, refresh) { |
- if (this.rescanTime_) { |
- if (this.rescanTime_ <= Date.now() + delay) |
- return; |
- clearTimeout(this.rescanTimeoutId_); |
- } |
- |
- var sequence = this.changeDirectorySequence_; |
- |
- this.rescanTime_ = Date.now() + delay; |
- this.rescanTimeoutId_ = setTimeout(function() { |
- this.rescanTimeoutId_ = null; |
- if (sequence === this.changeDirectorySequence_) |
- this.rescan(refresh); |
- }.bind(this), delay); |
-}; |
- |
-/** |
- * Cancel a rescan on timeout if it is scheduled. |
- * @private |
- */ |
-DirectoryModel.prototype.clearRescanTimeout_ = function() { |
- this.rescanTime_ = null; |
- if (this.rescanTimeoutId_) { |
- clearTimeout(this.rescanTimeoutId_); |
- this.rescanTimeoutId_ = null; |
- } |
-}; |
- |
-/** |
- * Rescan current directory. May be called indirectly through rescanLater or |
- * directly in order to reflect user action. Will first cache all the directory |
- * contents in an array, then seamlessly substitute the fileList contents, |
- * preserving the select element etc. |
- * |
- * This should be to scan the contents of current directory (or search). |
- * |
- * @param {boolean} refresh True to refresh metadata, or false to use cached |
- * one. |
- */ |
-DirectoryModel.prototype.rescan = function(refresh) { |
- this.clearRescanTimeout_(); |
- if (this.runningScan_) { |
- this.pendingRescan_ = true; |
- return; |
- } |
- |
- var dirContents = this.currentDirContents_.clone(); |
- dirContents.setFileList([]); |
- |
- var sequence = this.changeDirectorySequence_; |
- |
- var successCallback = (function() { |
- if (sequence === this.changeDirectorySequence_) { |
- this.replaceDirectoryContents_(dirContents); |
- cr.dispatchSimpleEvent(this, 'rescan-completed'); |
- } |
- }).bind(this); |
- |
- this.scan_(dirContents, |
- refresh, |
- successCallback, function() {}, function() {}, function() {}); |
-}; |
- |
-/** |
- * Run scan on the current DirectoryContents. The active fileList is cleared and |
- * the entries are added directly. |
- * |
- * This should be used when changing directory or initiating a new search. |
- * |
- * @param {DirectoryContents} newDirContents New DirectoryContents instance to |
- * replace currentDirContents_. |
- * @param {function(boolean)} callback Callback with result. True if the scan |
- * is completed successfully, false if the scan is failed. |
- * @private |
- */ |
-DirectoryModel.prototype.clearAndScan_ = function(newDirContents, |
- callback) { |
- if (this.currentDirContents_.isScanning()) |
- this.currentDirContents_.cancelScan(); |
- this.currentDirContents_.dispose(); |
- this.currentDirContents_ = newDirContents; |
- this.clearRescanTimeout_(); |
- |
- if (this.pendingScan_) |
- this.pendingScan_ = false; |
- |
- if (this.runningScan_) { |
- if (this.runningScan_.isScanning()) |
- this.runningScan_.cancelScan(); |
- this.runningScan_ = null; |
- } |
- |
- var sequence = this.changeDirectorySequence_; |
- var cancelled = false; |
- |
- var onDone = function() { |
- if (cancelled) |
- return; |
- |
- cr.dispatchSimpleEvent(this, 'scan-completed'); |
- callback(true); |
- }.bind(this); |
- |
- var onFailed = function() { |
- if (cancelled) |
- return; |
- |
- cr.dispatchSimpleEvent(this, 'scan-failed'); |
- callback(false); |
- }.bind(this); |
- |
- var onUpdated = function() { |
- if (cancelled) |
- return; |
- |
- if (this.changeDirectorySequence_ !== sequence) { |
- cancelled = true; |
- cr.dispatchSimpleEvent(this, 'scan-cancelled'); |
- callback(false); |
- return; |
- } |
- |
- cr.dispatchSimpleEvent(this, 'scan-updated'); |
- }.bind(this); |
- |
- var onCancelled = function() { |
- if (cancelled) |
- return; |
- |
- cancelled = true; |
- cr.dispatchSimpleEvent(this, 'scan-cancelled'); |
- callback(false); |
- }.bind(this); |
- |
- // Clear the table, and start scanning. |
- cr.dispatchSimpleEvent(this, 'scan-started'); |
- var fileList = this.getFileList(); |
- fileList.splice(0, fileList.length); |
- this.scan_(this.currentDirContents_, false, |
- onDone, onFailed, onUpdated, onCancelled); |
-}; |
- |
-/** |
- * Adds/removes/updates items of file list. |
- * @param {Array.<Entry>} changedEntries Entries of updated/added files. |
- * @param {Array.<string>} removedUrls URLs of removed files. |
- * @private |
- */ |
-DirectoryModel.prototype.partialUpdate_ = |
- function(changedEntries, removedUrls) { |
- // This update should be included in the current running update. |
- if (this.pendingScan_) |
- return; |
- |
- if (this.runningScan_) { |
- // Do update after the current scan is finished. |
- var previousScan = this.runningScan_; |
- var onPreviousScanCompleted = function() { |
- previousScan.removeEventListener('scan-completed', |
- onPreviousScanCompleted); |
- // Run the update asynchronously. |
- Promise.resolve().then(function() { |
- this.partialUpdate_(changedEntries, removedUrls); |
- }.bind(this)); |
- }.bind(this); |
- previousScan.addEventListener('scan-completed', onPreviousScanCompleted); |
- return; |
- } |
- |
- var onFinish = function() { |
- this.runningScan_ = null; |
- |
- this.currentDirContents_.removeEventListener( |
- 'scan-completed', onCompleted); |
- this.currentDirContents_.removeEventListener('scan-failed', onFailure); |
- this.currentDirContents_.removeEventListener( |
- 'scan-cancelled', onCancelled); |
- }.bind(this); |
- |
- var onCompleted = function() { |
- onFinish(); |
- cr.dispatchSimpleEvent(this, 'rescan-completed'); |
- }.bind(this); |
- |
- var onFailure = function() { |
- onFinish(); |
- }; |
- |
- var onCancelled = function() { |
- onFinish(); |
- }; |
- |
- this.runningScan_ = this.currentDirContents_; |
- this.currentDirContents_.addEventListener('scan-completed', onCompleted); |
- this.currentDirContents_.addEventListener('scan-failed', onFailure); |
- this.currentDirContents_.addEventListener('scan-cancelled', onCancelled); |
- this.currentDirContents_.update(changedEntries, removedUrls); |
-}; |
- |
-/** |
- * Perform a directory contents scan. Should be called only from rescan() and |
- * clearAndScan_(). |
- * |
- * @param {DirectoryContents} dirContents DirectoryContents instance on which |
- * the scan will be run. |
- * @param {boolean} refresh True to refresh metadata, or false to use cached |
- * one. |
- * @param {function()} successCallback Callback on success. |
- * @param {function()} failureCallback Callback on failure. |
- * @param {function()} updatedCallback Callback on update. Only on the last |
- * update, {@code successCallback} is called instead of this. |
- * @param {function()} cancelledCallback Callback on cancel. |
- * @private |
- */ |
-DirectoryModel.prototype.scan_ = function( |
- dirContents, |
- refresh, |
- successCallback, failureCallback, updatedCallback, cancelledCallback) { |
- var self = this; |
- |
- /** |
- * Runs pending scan if there is one. |
- * |
- * @return {boolean} Did pending scan exist. |
- */ |
- var maybeRunPendingRescan = function() { |
- if (this.pendingRescan_) { |
- this.rescanSoon(refresh); |
- this.pendingRescan_ = false; |
- return true; |
- } |
- return false; |
- }.bind(this); |
- |
- var onFinished = function() { |
- dirContents.removeEventListener('scan-completed', onSuccess); |
- dirContents.removeEventListener('scan-updated', updatedCallback); |
- dirContents.removeEventListener('scan-failed', onFailure); |
- dirContents.removeEventListener('scan-cancelled', cancelledCallback); |
- }; |
- |
- var onSuccess = function() { |
- onFinished(); |
- |
- // Record metric for Downloads directory. |
- if (!dirContents.isSearch()) { |
- var locationInfo = |
- this.volumeManager_.getLocationInfo(dirContents.getDirectoryEntry()); |
- if (locationInfo.volumeInfo.volumeType === |
- VolumeManagerCommon.VolumeType.DOWNLOADS && |
- locationInfo.isRootEntry) { |
- metrics.recordMediumCount('DownloadsCount', |
- dirContents.fileList_.length); |
- } |
- } |
- |
- this.runningScan_ = null; |
- successCallback(); |
- this.scanFailures_ = 0; |
- maybeRunPendingRescan(); |
- }.bind(this); |
- |
- var onFailure = function() { |
- onFinished(); |
- |
- this.runningScan_ = null; |
- this.scanFailures_++; |
- failureCallback(); |
- |
- if (maybeRunPendingRescan()) |
- return; |
- |
- if (this.scanFailures_ <= 1) |
- this.rescanLater(refresh); |
- }.bind(this); |
- |
- var onCancelled = function() { |
- onFinished(); |
- cancelledCallback(); |
- }; |
- |
- this.runningScan_ = dirContents; |
- |
- dirContents.addEventListener('scan-completed', onSuccess); |
- dirContents.addEventListener('scan-updated', updatedCallback); |
- dirContents.addEventListener('scan-failed', onFailure); |
- dirContents.addEventListener('scan-cancelled', onCancelled); |
- dirContents.scan(refresh); |
-}; |
- |
-/** |
- * @param {DirectoryContents} dirContents DirectoryContents instance. This must |
- * be a different instance from this.currentDirContents_. |
- * @private |
- */ |
-DirectoryModel.prototype.replaceDirectoryContents_ = function(dirContents) { |
- console.assert(this.currentDirContents_ !== dirContents, |
- 'Give directory contents instance must be different from current one.'); |
- cr.dispatchSimpleEvent(this, 'begin-update-files'); |
- this.updateSelectionAndPublishEvent_(this.fileListSelection_, function() { |
- var selectedEntries = this.getSelectedEntries_(); |
- var selectedIndices = this.fileListSelection_.selectedIndexes; |
- |
- // Restore leadIndex in case leadName no longer exists. |
- var leadIndex = this.fileListSelection_.leadIndex; |
- var leadEntry = this.getLeadEntry_(); |
- |
- var previousDirContents = this.currentDirContents_; |
- this.currentDirContents_ = dirContents; |
- this.currentDirContents_.replaceContextFileList(); |
- previousDirContents.dispose(); |
- |
- this.setSelectedEntries_(selectedEntries); |
- this.fileListSelection_.leadIndex = leadIndex; |
- this.setLeadEntry_(leadEntry); |
- |
- // If nothing is selected after update, then select file next to the |
- // latest selection |
- var forceChangeEvent = false; |
- if (this.fileListSelection_.selectedIndexes.length == 0 && |
- selectedIndices.length != 0) { |
- var maxIdx = Math.max.apply(null, selectedIndices); |
- this.selectIndex(Math.min(maxIdx - selectedIndices.length + 2, |
- this.getFileList().length) - 1); |
- forceChangeEvent = true; |
- } |
- return forceChangeEvent; |
- }.bind(this)); |
- |
- cr.dispatchSimpleEvent(this, 'end-update-files'); |
-}; |
- |
-/** |
- * Callback when an entry is changed. |
- * @param {util.EntryChangedKind} kind How the entry is changed. |
- * @param {Array.<Entry>} entries The changed entries. |
- */ |
-DirectoryModel.prototype.onEntriesChanged = function(kind, entries) { |
- // TODO(hidehiko): We should update directory model even the search result |
- // is shown. |
- var rootType = this.getCurrentRootType(); |
- if ((rootType === VolumeManagerCommon.RootType.DRIVE || |
- rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME || |
- rootType === VolumeManagerCommon.RootType.DRIVE_RECENT || |
- rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE) && |
- this.isSearching()) |
- return; |
- |
- switch (kind) { |
- case util.EntryChangedKind.CREATED: |
- var parentPromises = []; |
- for (var i = 0; i < entries.length; i++) { |
- parentPromises.push(new Promise(function(resolve, reject) { |
- entries[i].getParent(resolve, reject); |
- })); |
- } |
- Promise.all(parentPromises).then(function(parents) { |
- var entriesToAdd = []; |
- for (var i = 0; i < parents.length; i++) { |
- if (!util.isSameEntry(parents[i], this.getCurrentDirEntry())) |
- continue; |
- var index = this.findIndexByEntry_(entries[i]); |
- if (index >= 0) { |
- this.getFileList().replaceItem( |
- this.getFileList().item(index), entries[i]); |
- } else { |
- entriesToAdd.push(entries[i]); |
- } |
- } |
- this.partialUpdate_(entriesToAdd, []); |
- }.bind(this)).catch(function(error) { |
- console.error(error.stack || error); |
- }); |
- break; |
- |
- case util.EntryChangedKind.DELETED: |
- // This is the delete event. |
- this.partialUpdate_([], util.entriesToURLs(entries)); |
- break; |
- |
- default: |
- console.error('Invalid EntryChangedKind: ' + kind); |
- break; |
- } |
-}; |
- |
-/** |
- * @param {Entry} entry The entry to be searched. |
- * @return {number} The index in the fileList, or -1 if not found. |
- * @private |
- */ |
-DirectoryModel.prototype.findIndexByEntry_ = function(entry) { |
- var fileList = this.getFileList(); |
- for (var i = 0; i < fileList.length; i++) { |
- if (util.isSameEntry(/** @type {Entry} */ (fileList.item(i)), entry)) |
- return i; |
- } |
- return -1; |
-}; |
- |
-/** |
- * Called when rename is done successfully. |
- * Note: conceptually, DirectoryModel should work without this, because entries |
- * can be renamed by other systems anytime and Files.app should reflect it |
- * correctly. |
- * TODO(hidehiko): investigate more background, and remove this if possible. |
- * |
- * @param {Entry} oldEntry The old entry. |
- * @param {Entry} newEntry The new entry. |
- * @param {function()=} opt_callback Called on completion. |
- */ |
-DirectoryModel.prototype.onRenameEntry = function( |
- oldEntry, newEntry, opt_callback) { |
- this.currentDirContents_.prefetchMetadata([newEntry], true, function() { |
- // If the current directory is the old entry, then quietly change to the |
- // new one. |
- if (util.isSameEntry(oldEntry, this.getCurrentDirEntry())) |
- this.changeDirectoryEntry(newEntry); |
- |
- // Replace the old item with the new item. |
- // If the entry doesn't exist in the list, it has been updated from |
- // outside (probably by directory rescan) and is just ignored. |
- this.getFileList().replaceItem(oldEntry, newEntry); |
- |
- // Run callback, finally. |
- if (opt_callback) |
- opt_callback(); |
- }.bind(this)); |
-}; |
- |
-/** |
- * Creates directory and updates the file list. |
- * |
- * @param {string} name Directory name. |
- * @param {function(DirectoryEntry)} successCallback Callback on success. |
- * @param {function(DOMError)} errorCallback Callback on failure. |
- * @param {function()} abortCallback Callback on abort (cancelled by user). |
- */ |
-DirectoryModel.prototype.createDirectory = function(name, |
- successCallback, |
- errorCallback, |
- abortCallback) { |
- // Obtain and check the current directory. |
- var entry = this.getCurrentDirEntry(); |
- if (!entry || this.isSearching()) { |
- errorCallback(util.createDOMError( |
- util.FileError.INVALID_MODIFICATION_ERR)); |
- return; |
- } |
- |
- var sequence = this.changeDirectorySequence_; |
- |
- new Promise(entry.getDirectory.bind( |
- entry, name, {create: true, exclusive: true})). |
- |
- then(function(newEntry) { |
- // Refresh the cache. |
- this.metadataCache_.clear([newEntry], '*'); |
- return new Promise(function(onFulfilled, onRejected) { |
- this.metadataCache_.getOne(newEntry, |
- 'filesystem', |
- onFulfilled.bind(null, newEntry)); |
- }.bind(this)); |
- }.bind(this)). |
- |
- then(function(newEntry) { |
- // Do not change anything or call the callback if current |
- // directory changed. |
- if (this.changeDirectorySequence_ !== sequence) { |
- abortCallback(); |
- return; |
- } |
- |
- // If target directory is already in the list, just select it. |
- var existing = this.getFileList().slice().filter( |
- function(e) { return e.name === name; }); |
- if (existing.length) { |
- this.selectEntry(newEntry); |
- successCallback(existing[0]); |
- } else { |
- this.fileListSelection_.beginChange(); |
- this.getFileList().splice(0, 0, newEntry); |
- this.selectEntry(newEntry); |
- this.fileListSelection_.endChange(); |
- successCallback(newEntry); |
- } |
- }.bind(this), function(reason) { |
- errorCallback(/** @type {DOMError} */ (reason)); |
- }); |
-}; |
- |
-/** |
- * Changes the current directory to the directory represented by |
- * a DirectoryEntry or a fake entry. |
- * |
- * Dispatches the 'directory-changed' event when the directory is successfully |
- * changed. |
- * |
- * Note : if this is called from UI, please consider to use DirectoryModel. |
- * activateDirectoryEntry instead of this, which is higher-level function and |
- * cares about the selection. |
- * |
- * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to |
- * be opened. |
- * @param {function()=} opt_callback Executed if the directory loads |
- * successfully. |
- */ |
-DirectoryModel.prototype.changeDirectoryEntry = function( |
- dirEntry, opt_callback) { |
- // Increment the sequence value. |
- this.changeDirectorySequence_++; |
- this.clearSearch_(); |
- |
- this.directoryChangeQueue_.run(function(sequence, queueTaskCallback) { |
- this.fileWatcher_.changeWatchedDirectory( |
- dirEntry, |
- function() { |
- if (this.changeDirectorySequence_ !== sequence) { |
- queueTaskCallback(); |
- return; |
- } |
- |
- var newDirectoryContents = this.createDirectoryContents_( |
- this.currentFileListContext_, dirEntry, ''); |
- if (!newDirectoryContents) { |
- queueTaskCallback(); |
- return; |
- } |
- |
- var previousDirEntry = |
- this.currentDirContents_.getDirectoryEntry(); |
- this.clearAndScan_( |
- newDirectoryContents, |
- function(result) { |
- // Calls the callback of the method when successful. |
- if (result && opt_callback) |
- opt_callback(); |
- |
- // Notify that the current task of this.directoryChangeQueue_ |
- // is completed. |
- setTimeout(queueTaskCallback, 0); |
- }); |
- |
- // For tests that open the dialog to empty directories, everything |
- // is loaded at this point. |
- util.testSendMessage('directory-change-complete'); |
- |
- var event = new Event('directory-changed'); |
- event.previousDirEntry = previousDirEntry; |
- event.newDirEntry = dirEntry; |
- this.dispatchEvent(event); |
- }.bind(this)); |
- }.bind(this, this.changeDirectorySequence_)); |
-}; |
- |
-/** |
- * Activates the given directory. |
- * This method: |
- * - Changes the current directory, if the given directory is the current |
- * directory. |
- * - Clears the selection, if the given directory is the current directory. |
- * |
- * @param {DirectoryEntry|Object} dirEntry The entry of the new directory to |
- * be opened. |
- * @param {function()=} opt_callback Executed if the directory loads |
- * successfully. |
- */ |
-DirectoryModel.prototype.activateDirectoryEntry = function( |
- dirEntry, opt_callback) { |
- var currentDirectoryEntry = this.getCurrentDirEntry(); |
- if (currentDirectoryEntry && |
- util.isSameEntry(dirEntry, currentDirectoryEntry)) { |
- // On activating the current directory, clear the selection on the filelist. |
- this.clearSelection(); |
- } else { |
- // Otherwise, changes the current directory. |
- this.changeDirectoryEntry(dirEntry, opt_callback); |
- } |
-}; |
- |
-/** |
- * Clears the selection in the file list. |
- */ |
-DirectoryModel.prototype.clearSelection = function() { |
- this.setSelectedEntries_([]); |
-}; |
- |
-/** |
- * Creates an object which could say whether directory has changed while it has |
- * been active or not. Designed for long operations that should be cancelled |
- * if the used change current directory. |
- * @return {Object} Created object. |
- */ |
-DirectoryModel.prototype.createDirectoryChangeTracker = function() { |
- var tracker = { |
- dm_: this, |
- active_: false, |
- hasChanged: false, |
- |
- start: function() { |
- if (!this.active_) { |
- this.dm_.addEventListener('directory-changed', |
- this.onDirectoryChange_); |
- this.active_ = true; |
- this.hasChanged = false; |
- } |
- }, |
- |
- stop: function() { |
- if (this.active_) { |
- this.dm_.removeEventListener('directory-changed', |
- this.onDirectoryChange_); |
- this.active_ = false; |
- } |
- }, |
- |
- onDirectoryChange_: function(event) { |
- tracker.stop(); |
- tracker.hasChanged = true; |
- } |
- }; |
- return tracker; |
-}; |
- |
-/** |
- * @param {Entry} entry Entry to be selected. |
- */ |
-DirectoryModel.prototype.selectEntry = function(entry) { |
- var fileList = this.getFileList(); |
- for (var i = 0; i < fileList.length; i++) { |
- if (fileList.item(i).toURL() === entry.toURL()) { |
- this.selectIndex(i); |
- return; |
- } |
- } |
-}; |
- |
-/** |
- * @param {Array.<Entry>} entries Array of entries. |
- */ |
-DirectoryModel.prototype.selectEntries = function(entries) { |
- // URLs are needed here, since we are comparing Entries by URLs. |
- var urls = util.entriesToURLs(entries); |
- var fileList = this.getFileList(); |
- this.fileListSelection_.beginChange(); |
- this.fileListSelection_.unselectAll(); |
- for (var i = 0; i < fileList.length; i++) { |
- if (urls.indexOf(fileList.item(i).toURL()) >= 0) |
- this.fileListSelection_.setIndexSelected(i, true); |
- } |
- this.fileListSelection_.endChange(); |
-}; |
- |
-/** |
- * @param {number} index Index of file. |
- */ |
-DirectoryModel.prototype.selectIndex = function(index) { |
- // this.focusCurrentList_(); |
- if (index >= this.getFileList().length) |
- return; |
- |
- // If a list bound with the model it will do scrollIndexIntoView(index). |
- this.fileListSelection_.selectedIndex = index; |
-}; |
- |
-/** |
- * Handles update of VolumeInfoList. |
- * @param {Event} event Event of VolumeInfoList's 'splice'. |
- * @private |
- */ |
-DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) { |
- // When the volume where we are is unmounted, fallback to the default volume's |
- // root. If current directory path is empty, stop the fallback |
- // since the current directory is initializing now. |
- if (this.getCurrentDirEntry() && |
- !this.volumeManager_.getVolumeInfo(this.getCurrentDirEntry())) { |
- this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) { |
- this.changeDirectoryEntry(displayRoot); |
- }.bind(this)); |
- } |
-}; |
- |
-/** |
- * Creates directory contents for the entry and query. |
- * |
- * @param {FileListContext} context File list context. |
- * @param {DirectoryEntry} entry Current directory. |
- * @param {string=} opt_query Search query string. |
- * @return {DirectoryContents} Directory contents. |
- * @private |
- */ |
-DirectoryModel.prototype.createDirectoryContents_ = |
- function(context, entry, opt_query) { |
- var query = (opt_query || '').trimLeft(); |
- var locationInfo = this.volumeManager_.getLocationInfo(entry); |
- if (!locationInfo) |
- return null; |
- var canUseDriveSearch = this.volumeManager_.getDriveConnectionState().type !== |
- VolumeManagerCommon.DriveConnectionType.OFFLINE && |
- locationInfo.isDriveBased; |
- |
- if (query && canUseDriveSearch) { |
- // Drive search. |
- return DirectoryContents.createForDriveSearch(context, entry, query); |
- } else if (query) { |
- // Local search. |
- return DirectoryContents.createForLocalSearch(context, entry, query); |
- } if (locationInfo.isSpecialSearchRoot) { |
- // Drive special search. |
- var searchType; |
- switch (locationInfo.rootType) { |
- case VolumeManagerCommon.RootType.DRIVE_OFFLINE: |
- searchType = |
- DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE; |
- break; |
- case VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME: |
- searchType = |
- DriveMetadataSearchContentScanner.SearchType.SEARCH_SHARED_WITH_ME; |
- break; |
- case VolumeManagerCommon.RootType.DRIVE_RECENT: |
- searchType = |
- DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES; |
- break; |
- default: |
- // Unknown special search entry. |
- throw new Error('Unknown special search type.'); |
- } |
- return DirectoryContents.createForDriveMetadataSearch( |
- context, |
- entry, |
- searchType); |
- } else { |
- // Local fetch or search. |
- return DirectoryContents.createForDirectory(context, entry); |
- } |
-}; |
- |
-/** |
- * Performs search and displays results. The search type is dependent on the |
- * current directory. If we are currently on drive, server side content search |
- * over drive mount point. If the current directory is not on the drive, file |
- * name search over current directory will be performed. |
- * |
- * @param {string} query Query that will be searched for. |
- * @param {function(Event)} onSearchRescan Function that will be called when the |
- * search directory is rescanned (i.e. search results are displayed). |
- * @param {function()} onClearSearch Function to be called when search state |
- * gets cleared. |
- * TODO(olege): Change callbacks to events. |
- */ |
-DirectoryModel.prototype.search = function(query, |
- onSearchRescan, |
- onClearSearch) { |
- this.clearSearch_(); |
- var currentDirEntry = this.getCurrentDirEntry(); |
- if (!currentDirEntry) { |
- // Not yet initialized. Do nothing. |
- return; |
- } |
- |
- this.changeDirectorySequence_++; |
- this.directoryChangeQueue_.run(function(sequence, callback) { |
- if (this.changeDirectorySequence_ !== sequence) { |
- callback(); |
- return; |
- } |
- |
- if (!(query || '').trimLeft()) { |
- if (this.isSearching()) { |
- var newDirContents = this.createDirectoryContents_( |
- this.currentFileListContext_, |
- currentDirEntry); |
- this.clearAndScan_(newDirContents, |
- callback); |
- } else { |
- callback(); |
- } |
- return; |
- } |
- |
- var newDirContents = this.createDirectoryContents_( |
- this.currentFileListContext_, currentDirEntry, query); |
- if (!newDirContents) { |
- callback(); |
- return; |
- } |
- |
- this.onSearchCompleted_ = onSearchRescan; |
- this.onClearSearch_ = onClearSearch; |
- this.addEventListener('scan-completed', this.onSearchCompleted_); |
- this.clearAndScan_(newDirContents, |
- callback); |
- }.bind(this, this.changeDirectorySequence_)); |
-}; |
- |
-/** |
- * In case the search was active, remove listeners and send notifications on |
- * its canceling. |
- * @private |
- */ |
-DirectoryModel.prototype.clearSearch_ = function() { |
- if (!this.isSearching()) |
- return; |
- |
- if (this.onSearchCompleted_) { |
- this.removeEventListener('scan-completed', this.onSearchCompleted_); |
- this.onSearchCompleted_ = null; |
- } |
- |
- if (this.onClearSearch_) { |
- this.onClearSearch_(); |
- this.onClearSearch_ = null; |
- } |
-}; |