diff options
Diffstat (limited to 'chromium/chrome/browser/resources/file_manager/foreground/js/directory_model.js')
-rw-r--r-- | chromium/chrome/browser/resources/file_manager/foreground/js/directory_model.js | 1186 |
1 files changed, 0 insertions, 1186 deletions
diff --git a/chromium/chrome/browser/resources/file_manager/foreground/js/directory_model.js b/chromium/chrome/browser/resources/file_manager/foreground/js/directory_model.js deleted file mode 100644 index fde41e8c321..00000000000 --- a/chromium/chrome/browser/resources/file_manager/foreground/js/directory_model.js +++ /dev/null @@ -1,1186 +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 = 1000; -// 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 - */ -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.fileFilter_ = fileFilter; - this.fileFilter_.addEventListener('changed', - this.onFilterChanged_.bind(this)); - - this.currentFileListContext_ = new FileListContext( - fileFilter, metadataCache); - this.currentDirContents_ = - DirectoryContents.createForDirectory(this.currentFileListContext_, null); - - 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)); -} - -/** - * Fake entry to be used in currentDirEntry_ when current directory is - * unmounted DRIVE. TODO(haruki): Support "drive/root" and "drive/other". - * @type {Object} - * @const - * @private - */ -DirectoryModel.fakeDriveEntry_ = { - fullPath: RootDirectory.DRIVE + '/' + DriveSubRootDirectory.ROOT, - isDirectory: true, - rootType: RootType.DRIVE -}; - -/** - * Fake entry representing a psuedo directory, which contains Drive files - * available offline. This entry works as a trigger to start a search for - * offline files. - * @type {Object} - * @const - * @private - */ -DirectoryModel.fakeDriveOfflineEntry_ = { - fullPath: RootDirectory.DRIVE_OFFLINE, - isDirectory: true, - rootType: RootType.DRIVE_OFFLINE -}; - -/** - * Fake entry representing a pseudo directory, which contains shared-with-me - * Drive files. This entry works as a trigger to start a search for - * shared-with-me files. - * @type {Object} - * @const - * @private - */ -DirectoryModel.fakeDriveSharedWithMeEntry_ = { - fullPath: RootDirectory.DRIVE_SHARED_WITH_ME, - isDirectory: true, - rootType: RootType.DRIVE_SHARED_WITH_ME -}; - -/** - * Fake entry representing a pseudo directory, which contains Drive files - * accessed recently. This entry works as a trigger to start a metadata search - * implemented as DirectoryContentsDriveRecent. - * DirectoryModel is responsible to start the search when the UI tries to open - * this fake entry (e.g. changeDirectory()). - * @type {Object} - * @const - * @private - */ -DirectoryModel.fakeDriveRecentEntry_ = { - fullPath: RootDirectory.DRIVE_RECENT, - isDirectory: true, - rootType: RootType.DRIVE_RECENT -}; - -/** - * List of fake entries for special searches. - * - * @type {Array.<Object>} - * @const - */ -DirectoryModel.FAKE_DRIVE_SPECIAL_SEARCH_ENTRIES = [ - DirectoryModel.fakeDriveSharedWithMeEntry_, - DirectoryModel.fakeDriveRecentEntry_, - DirectoryModel.fakeDriveOfflineEntry_ -]; - -/** - * 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; -}; - -/** - * Sort the file list. - * @param {string} sortField Sort field. - * @param {string} sortDirection "asc" or "desc". - */ -DirectoryModel.prototype.sortFileList = function(sortField, sortDirection) { - this.getFileList().sort(sortField, sortDirection); -}; - -/** - * @return {cr.ui.ListSelectionModel|cr.ui.ListSingleSelectionModel} Selection - * in the fileList. - */ -DirectoryModel.prototype.getFileListSelection = function() { - return this.fileListSelection_; -}; - -/** - * @return {RootType} Root type of current root. - */ -DirectoryModel.prototype.getCurrentRootType = function() { - var entry = this.currentDirContents_.getDirectoryEntry(); - return PathUtil.getRootType(entry ? entry.fullPath : ''); -}; - -/** - * @return {string} Root path. - */ -DirectoryModel.prototype.getCurrentRootPath = function() { - var entry = this.currentDirContents_.getDirectoryEntry(); - return entry ? PathUtil.getRootPath(entry.fullPath) : ''; -}; - -/** - * @return {string} Filesystem URL representing the mountpoint for the current - * contents. - */ -DirectoryModel.prototype.getCurrentMountPointUrl = function() { - var rootPath = this.getCurrentRootPath(); - // Special search roots are just showing a search results from DRIVE. - if (PathUtil.getRootType(rootPath) == RootType.DRIVE || - PathUtil.isSpecialSearchRoot(rootPath)) - return util.makeFilesystemUrl(RootDirectory.DRIVE); - - return util.makeFilesystemUrl(rootPath); -}; - -/** - * @return {boolean} on True if offline. - */ -DirectoryModel.prototype.isDriveOffline = function() { - var connection = this.volumeManager_.getDriveConnectionState(); - return connection.type == util.DriveConnectionType.OFFLINE; -}; - -/** - * TODO(haruki): This actually checks the current root. Fix the method name and - * related code. - * @return {boolean} True if the root for the current directory is read only. - */ -DirectoryModel.prototype.isReadOnly = function() { - return this.isPathReadOnly(this.getCurrentRootPath()); -}; - -/** - * @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(); -}; - -/** - * @param {string} path Path to check. - * @return {boolean} True if the |path| is read only. - */ -DirectoryModel.prototype.isPathReadOnly = function(path) { - // TODO(hidehiko): Migrate this into VolumeInfo. - switch (PathUtil.getRootType(path)) { - case RootType.REMOVABLE: - var volumeInfo = this.volumeManager_.getVolumeInfo(path); - // Returns true if the volume is actually read only, or if an error - // is found during the mounting. - // TODO(hidehiko): Remove "error" check here, by removing error'ed volume - // info from VolumeManager. - return volumeInfo && (volumeInfo.isReadOnly || !!volumeInfo.error); - case RootType.ARCHIVE: - return true; - case RootType.DOWNLOADS: - return false; - case RootType.DRIVE: - // TODO(haruki): Maybe add DRIVE_OFFLINE as well to allow renaming in the - // offline tab. - return this.isDriveOffline(); - default: - return true; - } -}; - -/** - * 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. - * @private - */ -DirectoryModel.prototype.onWatcherDirectoryChanged_ = function() { - this.rescanSoon(); -}; - -/** - * Invoked when filters are changed. - * @private - */ -DirectoryModel.prototype.onFilterChanged_ = function() { - this.rescanSoon(); -}; - -/** - * 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 {string} URL of the current directory. or null if unavailable. - */ -DirectoryModel.prototype.getCurrentDirectoryURL = function() { - var entry = this.currentDirContents_.getDirectoryEntry(); - if (!entry) - return null; - if (entry === DirectoryModel.fakeDriveOfflineEntry_) - return util.makeFilesystemUrl(entry.fullPath); - return entry.toURL(); -}; - -/** - * @return {string} Path for the current directory, or empty string if the - * current directory is not yet set. - */ -DirectoryModel.prototype.getCurrentDirPath = function() { - var entry = this.currentDirContents_.getDirectoryEntry(); - return entry ? entry.fullPath : ''; -}; - -/** - * @return {Array.<string>} File paths of selected files. - * @private - */ -DirectoryModel.prototype.getSelectedPaths_ = function() { - var indexes = this.fileListSelection_.selectedIndexes; - var fileList = this.getFileList(); - if (fileList) { - return indexes.map(function(i) { - return fileList.item(i).fullPath; - }); - } - return []; -}; - -/** - * @param {Array.<string>} value List of file paths of selected files. - * @private - */ -DirectoryModel.prototype.setSelectedPaths_ = function(value) { - var indexes = []; - var fileList = this.getFileList(); - - var safeKey = function(key) { - // The transformation must: - // 1. Never generate a reserved name ('__proto__') - // 2. Keep different keys different. - return '#' + key; - }; - - var hash = {}; - - for (var i = 0; i < value.length; i++) - hash[safeKey(value[i])] = 1; - - for (var i = 0; i < fileList.length; i++) { - if (hash.hasOwnProperty(safeKey(fileList.item(i).fullPath))) - indexes.push(i); - } - this.fileListSelection_.selectedIndexes = indexes; -}; - -/** - * @return {string} Lead item file path. - * @private - */ -DirectoryModel.prototype.getLeadPath_ = function() { - var index = this.fileListSelection_.leadIndex; - return index >= 0 && this.getFileList().item(index).fullPath; -}; - -/** - * @param {string} value The name of new lead index. - * @private - */ -DirectoryModel.prototype.setLeadPath_ = function(value) { - var fileList = this.getFileList(); - for (var i = 0; i < fileList.length; i++) { - if (fileList.item(i).fullPath === value) { - this.fileListSelection_.leadIndex = i; - return; - } - } -}; - -/** - * Schedule rescan with short delay. - */ -DirectoryModel.prototype.rescanSoon = function() { - this.scheduleRescan(SHORT_RESCAN_INTERVAL); -}; - -/** - * Schedule rescan with delay. Designed to handle directory change - * notification. - */ -DirectoryModel.prototype.rescanLater = function() { - this.scheduleRescan(SIMULTANEOUS_RESCAN_INTERVAL); -}; - -/** - * 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. - */ -DirectoryModel.prototype.scheduleRescan = function(delay) { - if (this.rescanTime_) { - if (this.rescanTime_ <= Date.now() + delay) - return; - clearTimeout(this.rescanTimeoutId_); - } - - this.rescanTime_ = Date.now() + delay; - this.rescanTimeoutId_ = setTimeout(this.rescan.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). - */ -DirectoryModel.prototype.rescan = function() { - this.clearRescanTimeout_(); - if (this.runningScan_) { - this.pendingRescan_ = true; - return; - } - - var dirContents = this.currentDirContents_.clone(); - dirContents.setFileList([]); - - var successCallback = (function() { - this.replaceDirectoryContents_(dirContents); - cr.dispatchSimpleEvent(this, 'rescan-completed'); - }).bind(this); - - this.scan_(dirContents, - 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 {DirectoryContentes} newDirContents New DirectoryContents instance to - * replace currentDirContents_. - * @param {function()=} opt_callback Called on success. - * @private - */ -DirectoryModel.prototype.clearAndScan_ = function(newDirContents, - opt_callback) { - if (this.currentDirContents_.isScanning()) - this.currentDirContents_.cancelScan(); - 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 onDone = function() { - cr.dispatchSimpleEvent(this, 'scan-completed'); - if (opt_callback) - opt_callback(); - }.bind(this); - - var onFailed = function() { - cr.dispatchSimpleEvent(this, 'scan-failed'); - }.bind(this); - - var onUpdated = function() { - cr.dispatchSimpleEvent(this, 'scan-updated'); - }.bind(this); - - var onCancelled = function() { - cr.dispatchSimpleEvent(this, 'scan-cancelled'); - }.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_, - onDone, onFailed, onUpdated, onCancelled); -}; - -/** - * 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 {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, - 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(); - this.pendingRescan_ = false; - return true; - } - return false; - }.bind(this); - - var onSuccess = function() { - // Record metric for Downloads directory. - if (!dirContents.isSearch()) { - var locationInfo = - this.volumeManager_.getLocationInfo(dirContents.getDirectoryEntry()); - if (locationInfo.volumeInfo.volumeType === util.VolumeType.DOWNLOADS && - locationInfo.isRootEntry) { - metrics.recordMediumCount('DownloadsCount', - dirContents.fileList_.length); - } - } - - this.runningScan_ = null; - successCallback(); - this.scanFailures_ = 0; - maybeRunPendingRescan(); - }.bind(this); - - var onFailure = function() { - this.runningScan_ = null; - this.scanFailures_++; - failureCallback(); - - if (maybeRunPendingRescan()) - return; - - if (this.scanFailures_ <= 1) - this.rescanLater(); - }.bind(this); - - this.runningScan_ = dirContents; - - dirContents.addEventListener('scan-completed', onSuccess); - dirContents.addEventListener('scan-updated', updatedCallback); - dirContents.addEventListener('scan-failed', onFailure); - dirContents.addEventListener('scan-cancelled', cancelledCallback); - dirContents.scan(); -}; - -/** - * @param {DirectoryContents} dirContents DirectoryContents instance. - * @private - */ -DirectoryModel.prototype.replaceDirectoryContents_ = function(dirContents) { - cr.dispatchSimpleEvent(this, 'begin-update-files'); - this.updateSelectionAndPublishEvent_(this.fileListSelection_, function() { - var selectedPaths = this.getSelectedPaths_(); - var selectedIndices = this.fileListSelection_.selectedIndexes; - - // Restore leadIndex in case leadName no longer exists. - var leadIndex = this.fileListSelection_.leadIndex; - var leadPath = this.getLeadPath_(); - - this.currentDirContents_ = dirContents; - dirContents.replaceContextFileList(); - - this.setSelectedPaths_(selectedPaths); - this.fileListSelection_.leadIndex = leadIndex; - this.setLeadPath_(leadPath); - - // 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 {Entry} entry The changed entry. - */ -DirectoryModel.prototype.onEntryChanged = function(kind, entry) { - // TODO(hidehiko): We should update directory model even the search result - // is shown. - var rootType = this.getCurrentRootType(); - if ((rootType === RootType.DRIVE || - rootType === RootType.DRIVE_SHARED_WITH_ME || - rootType === RootType.DRIVE_RECENT || - rootType === RootType.DRIVE_OFFLINE) && - this.isSearching()) - return; - - if (kind == util.EntryChangedKind.CREATED) { - entry.getParent(function(parentEntry) { - if (this.getCurrentDirEntry().fullPath != parentEntry.fullPath) { - // Do nothing if current directory changed during async operations. - return; - } - this.currentDirContents_.prefetchMetadata([entry], function() { - if (this.getCurrentDirEntry().fullPath != parentEntry.fullPath) { - // Do nothing if current directory changed during async operations. - return; - } - - var index = this.findIndexByEntry_(entry); - if (index >= 0) - this.getFileList().splice(index, 1, entry); - else - this.getFileList().push(entry); - }.bind(this)); - }.bind(this)); - } else { - // This is the delete event. - var index = this.findIndexByEntry_(entry); - if (index >= 0) - this.getFileList().splice(index, 1); - } -}; - -/** - * @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(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], function() { - // If the current directory is the old entry, then quietly change to the - // new one. - if (util.isSameEntry(oldEntry, this.getCurrentDirEntry())) - this.changeDirectory(newEntry.fullPath); - - // Look for the old entry. - // If the entry doesn't exist in the list, it has been updated from - // outside (probably by directory rescan). - var index = this.findIndexByEntry_(oldEntry); - if (index >= 0) { - // Update the content list and selection status. - var wasSelected = this.fileListSelection_.getIndexSelected(index); - this.updateSelectionAndPublishEvent_(this.fileListSelection_, function() { - this.fileListSelection_.setIndexSelected(index, false); - this.getFileList().splice(index, 1, newEntry); - if (wasSelected) { - // We re-search the index, because splice may trigger sorting so that - // index may be stale. - this.fileListSelection_.setIndexSelected( - this.findIndexByEntry_(newEntry), true); - } - return true; - }.bind(this)); - } - - // 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(FileError)} errorCallback Callback on failure. - */ -DirectoryModel.prototype.createDirectory = function(name, successCallback, - errorCallback) { - var entry = this.getCurrentDirEntry(); - if (!entry) { - errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR)); - return; - } - - var tracker = this.createDirectoryChangeTracker(); - tracker.start(); - - var onSuccess = function(newEntry) { - // Do not change anything or call the callback if current - // directory changed. - tracker.stop(); - if (tracker.hasChanged) - return; - - 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); - } - }; - - this.currentDirContents_.createDirectory(name, onSuccess.bind(this), - errorCallback); -}; - -/** - * Changes directory. Causes 'directory-change' event. - * - * The directory will not be changed, if another request is started before it is - * finished. The error callback will not be called, and the event for the first - * request will not be invoked. - * - * @param {string} path New current directory path. - * @param {function(FileError)=} opt_errorCallback Executed if the change - * directory failed. - */ -DirectoryModel.prototype.changeDirectory = function(path, opt_errorCallback) { - this.changeDirectorySequence_++; - - if (PathUtil.isSpecialSearchRoot(path)) { - this.specialSearch(path, ''); - return; - } - - this.resolveDirectory( - path, - function(sequence, directoryEntry) { - if (this.changeDirectorySequence_ === sequence) - this.changeDirectoryEntry(directoryEntry); - }.bind(this, this.changeDirectorySequence_), - function(error) { - console.error('Error changing directory to ' + path + ': ', error); - if (opt_errorCallback) - opt_errorCallback(error); - }); -}; - -/** - * Resolves absolute directory path. Handles Drive stub. If the drive is - * mounting, callbacks will be called after the mount is completed. - * - * @param {string} path Path to the directory. - * @param {function(DirectoryEntry)} successCallback Success callback. - * @param {function(FileError)} errorCallback Error callback. - */ -DirectoryModel.prototype.resolveDirectory = function( - path, successCallback, errorCallback) { - if (PathUtil.getRootType(path) == RootType.DRIVE) { - if (!this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE)) { - errorCallback(util.createFileError(FileError.NOT_FOUND_ERR)); - return; - } - } - - var onError = function(error) { - // Handle the special case, when in offline mode, and there are no cached - // contents on the C++ side. In such case, let's display the stub. - // The INVALID_STATE_ERR error code is returned from the drive filesystem - // in such situation. - // - // TODO(mtomasz, hashimoto): Consider rewriting this logic. - // crbug.com/253464. - if (PathUtil.getRootType(path) == RootType.DRIVE && - error.code == FileError.INVALID_STATE_ERR) { - successCallback(DirectoryModel.fakeDriveEntry_); - return; - } - errorCallback(error); - }.bind(this); - - // TODO(mtomasz): Use Entry instead of a path. - this.volumeManager_.resolveAbsolutePath( - path, - function(entry) { - if (entry.isFile) { - onError(util.createFileError(FileError.TYPE_MISMATCH_ERR)); - return; - } - successCallback(entry); - }, - onError); -}; - -/** - * @param {DirectoryEntry} dirEntry The absolute path to the new directory. - * @param {function()=} opt_callback Executed if the directory loads - * successfully. - * @private - */ -DirectoryModel.prototype.changeDirectoryEntrySilent_ = function(dirEntry, - opt_callback) { - var onScanComplete = function() { - if (opt_callback) - opt_callback(); - // For tests that open the dialog to empty directories, everything - // is loaded at this point. - chrome.test.sendMessage('directory-change-complete'); - }; - this.clearAndScan_( - DirectoryContents.createForDirectory(this.currentFileListContext_, - dirEntry), - onScanComplete.bind(this)); -}; - -/** - * Change the current directory to the directory represented by a - * DirectoryEntry. - * - * Dispatches the 'directory-changed' event when the directory is successfully - * changed. - * - * @param {DirectoryEntry} dirEntry The absolute path to the new directory. - * @param {function()=} opt_callback Executed if the directory loads - * successfully. - */ -DirectoryModel.prototype.changeDirectoryEntry = function( - dirEntry, opt_callback) { - this.fileWatcher_.changeWatchedDirectory(dirEntry, function(sequence) { - if (this.changeDirectorySequence_ !== sequence) - return; - var previous = this.currentDirContents_.getDirectoryEntry(); - this.clearSearch_(); - this.changeDirectoryEntrySilent_(dirEntry, opt_callback); - - var e = new Event('directory-changed'); - e.previousDirEntry = previous; - e.newDirEntry = dirEntry; - this.dispatchEvent(e); - }.bind(this, this.changeDirectorySequence_)); -}; - -/** - * 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.<string>} 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; -}; - -/** - * Called when VolumeInfoList is updated. - * - * @param {Event} event Event of VolumeInfoList's 'splice'. - * @private - */ -DirectoryModel.prototype.onVolumeInfoListUpdated_ = function(event) { - var driveVolume = this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE); - if (driveVolume && !driveVolume.error) { - var currentDirEntry = this.getCurrentDirEntry(); - if (currentDirEntry) { - if (currentDirEntry === DirectoryModel.fakeDriveEntry_) { - // Replace the fake entry by real DirectoryEntry silently. - this.volumeManager_.resolveAbsolutePath( - DirectoryModel.fakeDriveEntry_.fullPath, - function(entry) { - // If the current entry is still fake drive entry, replace it. - if (this.getCurrentDirEntry() === DirectoryModel.fakeDriveEntry_) - this.changeDirectoryEntrySilent_(entry); - }, - function(error) {}); - } else if (PathUtil.isSpecialSearchRoot(currentDirEntry.fullPath)) { - for (var i = 0; i < event.added.length; i++) { - if (event.added[i].volumeType == util.VolumeType.DRIVE) { - // If the Drive volume is newly mounted, rescan it. - this.rescan(); - break; - } - } - } - } - } - - // When the volume where we are is unmounted, fallback to - // DEFAULT_MOUNT_POINT. If current directory path is empty, stop the fallback - // since the current directory is initializing now. - // TODO(mtomasz): DEFAULT_MOUNT_POINT is deprecated. Use VolumeManager:: - // getDefaultVolume() after it is implemented. - if (this.getCurrentDirPath() && - !this.volumeManager_.getVolumeInfo(this.getCurrentDirPath())) - this.changeDirectory(PathUtil.DEFAULT_MOUNT_POINT); -}; - -/** - * Check if the root of the given path is mountable or not. - * - * @param {string} path Path. - * @return {boolean} Return true, if the given path is under mountable root. - * Otherwise, return false. - */ -DirectoryModel.isMountableRoot = function(path) { - var rootType = PathUtil.getRootType(path); - switch (rootType) { - case RootType.DOWNLOADS: - return false; - case RootType.ARCHIVE: - case RootType.REMOVABLE: - case RootType.DRIVE: - return true; - default: - throw new Error('Unknown root type!'); - } -}; - -/** - * 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) { - query = query.trimLeft(); - - this.clearSearch_(); - - var currentDirEntry = this.getCurrentDirEntry(); - if (!currentDirEntry) { - // Not yet initialized. Do nothing. - return; - } - - if (!query) { - if (this.isSearching()) { - var newDirContents = DirectoryContents.createForDirectory( - this.currentFileListContext_, - this.currentDirContents_.getLastNonSearchDirectoryEntry()); - this.clearAndScan_(newDirContents); - } - return; - } - - this.onSearchCompleted_ = onSearchRescan; - this.onClearSearch_ = onClearSearch; - - this.addEventListener('scan-completed', this.onSearchCompleted_); - - // If we are offline, let's fallback to file name search inside dir. - // A search initiated from directories in Drive or special search results - // should trigger Drive search. - var newDirContents; - if (!this.isDriveOffline() && - PathUtil.isDriveBasedPath(currentDirEntry.fullPath)) { - // Drive search is performed over the whole drive, so pass drive root as - // |directoryEntry|. - newDirContents = DirectoryContents.createForDriveSearch( - this.currentFileListContext_, - currentDirEntry, - this.currentDirContents_.getLastNonSearchDirectoryEntry(), - query); - } else { - newDirContents = DirectoryContents.createForLocalSearch( - this.currentFileListContext_, currentDirEntry, query); - } - this.clearAndScan_(newDirContents); -}; - -/** - * Performs special search and displays results. e.g. Drive files available - * offline, shared-with-me files, recently modified files. - * @param {string} path Path string representing special search. See fake - * entries in PathUtil.RootDirectory. - * @param {string=} opt_query Query string used for the search. - */ -DirectoryModel.prototype.specialSearch = function(path, opt_query) { - var query = opt_query || ''; - - this.clearSearch_(); - - this.onSearchCompleted_ = null; - this.onClearSearch_ = null; - - var onDriveDirectoryResolved = function(sequence, driveRoot) { - if (this.changeDirectorySequence_ !== sequence) - return; - if (!driveRoot || driveRoot == DirectoryModel.fakeDriveEntry_) { - // Drive root not available or not ready. onVolumeInfoListUpdated_() - // handles the rescan if necessary. - driveRoot = null; - } - - var specialSearchType = PathUtil.getRootType(path); - var searchOption; - var dirEntry; - if (specialSearchType == RootType.DRIVE_OFFLINE) { - dirEntry = DirectoryModel.fakeDriveOfflineEntry_; - searchOption = - DriveMetadataSearchContentScanner.SearchType.SEARCH_OFFLINE; - } else if (specialSearchType == RootType.DRIVE_SHARED_WITH_ME) { - dirEntry = DirectoryModel.fakeDriveSharedWithMeEntry_; - searchOption = - DriveMetadataSearchContentScanner.SearchType.SEARCH_SHARED_WITH_ME; - } else if (specialSearchType == RootType.DRIVE_RECENT) { - dirEntry = DirectoryModel.fakeDriveRecentEntry_; - searchOption = - DriveMetadataSearchContentScanner.SearchType.SEARCH_RECENT_FILES; - } else { - // Unknown path. - throw new Error('Unknown path for special search.'); - } - - var newDirContents = DirectoryContents.createForDriveMetadataSearch( - this.currentFileListContext_, - dirEntry, driveRoot, query, searchOption); - var previous = this.currentDirContents_.getDirectoryEntry(); - this.clearAndScan_(newDirContents); - - var e = new Event('directory-changed'); - e.previousDirEntry = previous; - e.newDirEntry = dirEntry; - this.dispatchEvent(e); - }.bind(this, this.changeDirectorySequence_); - - this.resolveDirectory(DirectoryModel.fakeDriveEntry_.fullPath, - onDriveDirectoryResolved /* success */, - function() {} /* failed */); -}; - -/** - * 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; - } -}; |