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.
8 * Entry of NavigationListModel. This constructor should be called only from
9 * the helper methods (NavigationModelItem.create).
11 * @param {string} path Path.
12 * @param {DirectoryEntry} entry Entry. Can be null.
15 function NavigationModelItem(path, entry) {
18 this.resolvingQueue_ = new AsyncUtil.Queue();
23 NavigationModelItem.prototype = {
24 get path() { return this.path_; },
28 * Returns the cached entry of the item. This may return NULL if the target is
29 * not available on the filesystem, is not resolved or is under resolving the
32 * @return {Entry} Cached entry.
34 NavigationModelItem.prototype.getCachedEntry = function() {
39 * @param {VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance.
40 * @param {string} path Path.
41 * @param {DirectoryEntry} entry Entry. Can be null.
42 * @param {function(FileError)} errorCallback Called when the resolving is
43 * failed with the error.
44 * @return {NavigationModelItem} Created NavigationModelItem.
46 NavigationModelItem.create = function(
47 volumeManager, path, entry, errorCallback) {
48 var item = new NavigationModelItem(path, entry);
50 // If the given entry is null, try to resolve path to get an entry.
52 item.resolvingQueue_.run(function(continueCallback) {
53 volumeManager.resolvePath(
56 if (entry.isDirectory)
59 errorCallback(util.createFileError(FileError.TYPE_MISMATCH_ERR));
72 * Retrieves the entry. If the entry is being retrieved, waits until it
74 * @param {function(Entry)} callback Called with the resolved entry. The entry
75 * may be NULL if resolving is failed.
77 NavigationModelItem.prototype.getEntryAsync = function(callback) {
78 // If resolving the entry is running, wait until it finishes.
79 this.resolvingQueue_.run(function(continueCallback) {
80 callback(this.entry_);
86 * Returns if this item is a shortcut or a volume root.
87 * @return {boolean} True if a shortcut, false if a volume root.
89 NavigationModelItem.prototype.isShortcut = function() {
90 return !PathUtil.isRootPath(this.path_);
94 * A navigation list model. This model combines the 2 lists.
95 * @param {VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance.
96 * @param {cr.ui.ArrayDataModel} shortcutListModel The list of folder shortcut.
98 * @extends {cr.EventTarget}
100 function NavigationListModel(volumeManager, shortcutListModel) {
101 cr.EventTarget.call(this);
103 this.volumeManager_ = volumeManager;
104 this.shortcutListModel_ = shortcutListModel;
106 var volumeInfoToModelItem = function(volumeInfo) {
107 if (volumeInfo.volumeType == util.VolumeType.DRIVE) {
108 // For drive volume, we assign the path to "My Drive".
109 return NavigationModelItem.create(
111 volumeInfo.mountPath + '/root',
115 return NavigationModelItem.create(
117 volumeInfo.mountPath,
123 var pathToModelItem = function(path) {
124 var item = NavigationModelItem.create(
127 null, // Entry will be resolved.
129 if (error.code == FileError.NOT_FOUND_ERR)
130 this.onItemNotFoundError(item);
136 * Type of updated list.
144 Object.freeze(ListType);
146 // Generates this.volumeList_ and this.shortcutList_ from the models.
148 this.volumeManager_.volumeInfoList.slice().map(volumeInfoToModelItem);
150 this.shortcutList_ = [];
151 for (var i = 0; i < this.shortcutListModel_.length; i++) {
152 var shortcutPath = this.shortcutListModel_.item(i);
153 var mountPath = PathUtil.isDriveBasedPath(shortcutPath) ?
154 RootDirectory.DRIVE :
155 PathUtil.getRootPath(shortcutPath);
156 var volumeInfo = this.volumeManager_.getVolumeInfo(mountPath);
157 var isMounted = volumeInfo && !volumeInfo.error;
159 this.shortcutList_.push(pathToModelItem(shortcutPath));
162 // Generates a combined 'permuted' event from an event of either list.
163 var permutedHandler = function(listType, event) {
166 // Build the volumeList.
167 if (listType == ListType.VOLUME_LIST) {
168 // The volume is mounted or unmounted.
171 // Use the old instances if they just move.
172 for (var i = 0; i < event.permutation.length; i++) {
173 if (event.permutation[i] >= 0)
174 newList[event.permutation[i]] = this.volumeList_[i];
177 // Create missing instances.
178 for (var i = 0; i < event.newLength; i++) {
180 newList[i] = volumeInfoToModelItem(
181 this.volumeManager_.volumeInfoList.item(i));
184 this.volumeList_ = newList;
186 permutation = event.permutation.slice();
188 // volumeList part has not been changed, so the permutation should be
189 // idenetity mapping.
191 for (var i = 0; i < this.volumeList_.length; i++)
195 // Build the shortcutList. Even if the event is for the volumeInfoList
196 // update, the short cut path may be unmounted or newly mounted. So, here
197 // shortcutList will always be re-built.
198 // Currently this code may be redundant, as shortcut folder is supported
199 // only on Drive File System and we can assume single-profile, but
200 // multi-profile will be supported later.
201 // The shortcut list is sorted in case-insensitive lexicographical order.
202 // So we just can traverse the two list linearly.
204 var oldListIndex = 0;
206 while (modelIndex < this.shortcutListModel_.length &&
207 oldListIndex < this.shortcutList_.length) {
208 var shortcutPath = this.shortcutListModel_.item(modelIndex);
209 var cmp = this.shortcutListModel_.compare(
210 shortcutPath, this.shortcutList_[oldListIndex].path);
212 // The shortcut at shortcutList_[oldListIndex] is removed.
213 permutation.push(-1);
218 // Check if the volume where the shortcutPath is is mounted or not.
219 var mountPath = PathUtil.isDriveBasedPath(shortcutPath) ?
220 RootDirectory.DRIVE :
221 PathUtil.getRootPath(shortcutPath);
222 var volumeInfo = this.volumeManager_.getVolumeInfo(mountPath);
223 var isMounted = volumeInfo && !volumeInfo.error;
225 // There exists an old NavigationModelItem instance.
227 // Reuse the old instance.
228 permutation.push(newList.length + this.volumeList_.length);
229 newList.push(this.shortcutList_[oldListIndex]);
231 permutation.push(-1);
235 // We needs to create a new instance for the shortcut path.
237 newList.push(pathToModelItem(shortcutPath));
242 // Add remaining (new) shortcuts if necessary.
243 for (; modelIndex < this.shortcutListModel_.length; modelIndex++) {
244 var shortcutPath = this.shortcutListModel_.item(modelIndex);
245 var mountPath = PathUtil.isDriveBasedPath(shortcutPath) ?
246 RootDirectory.DRIVE :
247 PathUtil.getRootPath(shortcutPath);
248 var volumeInfo = this.volumeManager_.getVolumeInfo(mountPath);
249 var isMounted = volumeInfo && !volumeInfo.error;
251 newList.push(pathToModelItem(shortcutPath));
254 // Fill remaining permutation if necessary.
255 for (; oldListIndex < this.shortcutList_.length; oldListIndex++)
256 permutation.push(-1);
258 this.shortcutList_ = newList;
260 // Dispatch permuted event.
261 var permutedEvent = new Event('permuted');
262 permutedEvent.newLength =
263 this.volumeList_.length + this.shortcutList_.length;
264 permutedEvent.permutation = permutation;
265 this.dispatchEvent(permutedEvent);
268 this.volumeManager_.volumeInfoList.addEventListener(
269 'permuted', permutedHandler.bind(this, ListType.VOLUME_LIST));
270 this.shortcutListModel_.addEventListener(
271 'permuted', permutedHandler.bind(this, ListType.SHORTCUT_LIST));
273 // 'change' event is just ignored, because it is not fired neither in
274 // the folder shortcut list nor in the volume info list.
275 // 'splice' and 'sorted' events are not implemented, since they are not used
280 * NavigationList inherits cr.EventTarget.
282 NavigationListModel.prototype = {
283 __proto__: cr.EventTarget.prototype,
284 get length() { return this.length_(); },
285 get folderShortcutList() { return this.shortcutList_; }
289 * Returns the item at the given index.
290 * @param {number} index The index of the entry to get.
291 * @return {?string} The path at the given index.
293 NavigationListModel.prototype.item = function(index) {
294 var offset = this.volumeList_.length;
296 return this.volumeList_[index];
297 return this.shortcutList_[index - offset];
301 * Returns the number of items in the model.
302 * @return {number} The length of the model.
305 NavigationListModel.prototype.length_ = function() {
306 return this.volumeList_.length + this.shortcutList_.length;
310 * Returns the first matching item.
311 * @param {NavigationModelItem} modelItem The entry to find.
312 * @param {number=} opt_fromIndex If provided, then the searching start at
313 * the {@code opt_fromIndex}.
314 * @return {number} The index of the first found element or -1 if not found.
316 NavigationListModel.prototype.indexOf = function(modelItem, opt_fromIndex) {
317 for (var i = opt_fromIndex || 0; i < this.length; i++) {
318 if (modelItem === this.item(i))
325 * Called when one od the items is not found on the filesystem.
326 * @param {NavigationModelItem} modelItem The entry which is not found.
328 NavigationListModel.prototype.onItemNotFoundError = function(modelItem) {
329 var index = this.indexOf(modelItem);
331 // Invalid modelItem.
332 } else if (index < this.volumeList_.length) {
333 // The item is in the volume list.
335 // TODO(yoshiki): Implement it when necessary.
337 // The item is in the folder shortcut list.
338 if (this.isDriveMounted())
339 this.shortcutListModel_.remove(modelItem.path);
344 * Returns if the drive is mounted or not.
345 * @return {boolean} True if the drive is mounted, false otherwise.
347 NavigationListModel.prototype.isDriveMounted = function() {
348 return !!this.volumeManager_.getVolumeInfo(RootDirectory.DRIVE);