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

Side by Side Diff: chrome/browser/resources/file_manager/js/directory_contents.js

Issue 25278002: Extract ContentScanner from DirectoryContent. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 2 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
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 'use strict'; 5 'use strict';
6 6
7 /** 7 /**
8 * Scanner of the entries.
9 * @constructor
10 */
11 function ContentScanner() {
12 this.cancelled_ = false;
13 }
14
15 /**
16 * Starts to scan the entries. For example, starts to read the entries in a
17 * directory, or starts to search with some query on a file system.
18 * Derived classes must override this method.
19 *
20 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
21 * entries are read. This can be called a couple of times until the
22 * completion.
23 * @param {function()} successCallback Called when the scan is completed
24 * successfully.
25 * @param {function(FileError)} errorCallback Called an error occurs.
26 */
27 ContentScanner.prototype.scan = function(
28 entriesCallback, successCallback, errorCallback) {
29 };
30
31 /**
32 * Request cancelling of the running scan. When the cancelling is done,
33 * an error will be reported from errorCallback passed to scan().
34 */
35 ContentScanner.prototype.cancel = function() {
36 this.cancelled_ = true;
37 };
38
39 /**
40 * Scanner of the entries in a directory.
41 * @param {DirectoryEntry} entry The directory to be read.
42 * @constructor
43 * @extends {ContentScanner}
44 */
45 function DirectoryContentScanner(entry) {
46 ContentScanner.call(this);
47 this.entry_ = entry;
48 }
49
50 /**
51 * Extends ContentScanner.
52 */
53 DirectoryContentScanner.prototype.__proto__ = ContentScanner.prototype;
54
55 /**
56 * Starts to read the entries in the directory.
57 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
58 * entries are read. This can be called a couple of times until the
59 * completion.
60 * @param {function()} successCallback Called when the scan is completed
61 * successfully.
62 * @param {function(FileError)} errorCallback Called an error occurs.
63 * @override
64 */
65 DirectoryContentScanner.prototype.scan = function(
66 entriesCallback, successCallback, errorCallback) {
67 if (!this.entry_ || this.entry_ === DirectoryModel.fakeDriveEntry_) {
68 // If entry is not specified or a fake, we cannot read it.
69 errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR));
70 return;
71 }
72
73 metrics.startInterval('DirectoryScan');
74 var reader = this.entry_.createReader();
75 var readEntries = function() {
76 reader.readEntries(
77 function(entries) {
78 if (this.cancelled_) {
79 errorCallback(util.createFileError(FileError.ABORT_ERR));
80 return;
81 }
82
83 if (entries.length === 0) {
84 // All entries are read.
85 metrics.recordInterval('DirectoryScan');
86 successCallback();
87 return;
88 }
89
90 entriesCallback(entries);
91 readEntries();
92 }.bind(this),
93 errorCallback);
94 }.bind(this);
95 readEntries();
96 };
97
98 /**
99 * Scanner of the entries for the search results on Drive File System.
100 * @param {string} query The query string.
101 * @constructor
102 * @extends {ContentScanner}
103 */
104 function DriveSearchContentScanner(query) {
105 ContentScanner.call(this);
106 this.query_ = query;
107 }
108
109 /**
110 * Extends ContentScanner.
111 */
112 DriveSearchContentScanner.prototype.__proto__ = ContentScanner.prototype;
113
114 /**
115 * Delay in milliseconds to be used for drive search scan, in order to reduce
116 * the number of server requests while user is typing the query.
117 * @type {number}
118 * @private
119 * @const
120 */
121 DriveSearchContentScanner.SCAN_DELAY_ = 200;
122
123 /**
124 * Maximum number of results which is shown on the search.
125 * @type {number}
126 * @private
127 * @const
128 */
129 DriveSearchContentScanner.MAX_RESULTS_ = 100;
130
131 /**
132 * Starts to search on Drive File System.
133 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
134 * entries are read. This can be called a couple of times until the
135 * completion.
136 * @param {function()} successCallback Called when the scan is completed
137 * successfully.
138 * @param {function(FileError)} errorCallback Called an error occurs.
139 * @override
140 */
141 DriveSearchContentScanner.prototype.scan = function(
142 entriesCallback, successCallback, errorCallback) {
143 var numReadEntries = 0;
144 var readEntries = function(nextFeed) {
145 chrome.fileBrowserPrivate.searchDrive(
146 {query: this.query_, nextFeed: nextFeed},
147 function(entries, nextFeed) {
148 if (this.cancelled_) {
149 errorCallback(util.createFileError(FileError.ABORT_ERR));
150 return;
151 }
152
153 // TODO(tbarzic): Improve error handling.
154 if (!entries) {
155 console.error('Drive search encountered an error.');
156 errorCallback(util.createFileError(
157 FileError.INVALID_MODIFICATION_ERR));
158 return;
159 }
160
161 var numRemainingEntries =
162 DriveSearchContentScanner.MAX_RESULTS_ - numReadEntries;
163 if (entries.length >= numRemainingEntries) {
164 // The limit is hit, so quit the scan here.
165 entries = entries.slice(0, numRemainingEntries);
166 nextFeed = '';
167 }
168
169 numReadEntries += entries.length;
170 if (entries.length > 0)
171 entriesCallback(entries);
172
173 if (nextFeed === '')
174 successCallback();
175 else
176 readEntries(nextFeed);
177 }.bind(this));
178 }.bind(this);
179
180 // Let's give another search a chance to cancel us before we begin.
181 setTimeout(
182 function() {
183 // Check cancelled state before read the entries.
184 if (this.cancelled_) {
185 errorCallback(util.createFileError(FileError.ABORT_ERR));
186 return;
187 }
188 readEntries('');
189 }.bind(this),
190 DriveSearchContentScanner.SCAN_DELAY_);
191 };
192
193 /**
194 * Scanner of the entries of the file name search on the directory tree, whose
195 * root is entry.
196 * @param {DirectoryEntry} entry The root of the search target directory tree.
197 * @param {string} query The query of the search.
198 * @constructor
199 * @extends {ContentScanner}
200 */
201 function LocalSearchContentScanner(entry, query) {
202 ContentScanner.call(this);
203 this.entry_ = entry;
204 this.query_ = query.toLowerCase();
205 }
206
207 /**
208 * Extedns ContentScanner.
209 */
210 LocalSearchContentScanner.prototype.__proto__ = ContentScanner.prototype;
211
212 /**
213 * Starts the file name search.
214 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
215 * entries are read. This can be called a couple of times until the
216 * completion.
217 * @param {function()} successCallback Called when the scan is completed
218 * successfully.
219 * @param {function(FileError)} errorCallback Called an error occurs.
220 * @override
221 */
222 LocalSearchContentScanner.prototype.scan = function(
223 entriesCallback, successCallback, errorCallback) {
224 var numRunningTasks = 0;
225 var error = null;
226 var maybeRunCallback = function() {
227 if (numRunningTasks === 0) {
228 if (this.cancelled_)
229 errorCallback(util.createFileError(FileError.ABORT_ERR));
230 else if (error)
231 errorCallback(error);
232 else
233 successCallback();
234 }
235 }.bind(this);
236
237 var processEntry = function(entry) {
238 numRunningTasks++;
239 var onError = function(fileError) {
240 if (!error)
241 error = fileError;
242 numRunningTasks--;
243 maybeRunCallback();
244 };
245
246 var onSuccess = function(entries) {
247 if (this.cancelled_ || error || entries.length === 0) {
248 numRunningTasks--;
249 maybeRunCallback();
250 return;
251 }
252
253 // Filters by the query, and if found, run entriesCallback.
254 var foundEntries = entries.filter(function(entry) {
255 return entry.name.toLowerCase().indexOf(this.query_) >= 0;
256 }.bind(this));
257 if (foundEntries.length > 0)
258 entriesCallback(foundEntries);
259
260 // Start to process sub directories.
261 for (var i = 0; i < entries.length; i++) {
262 if (entries[i].isDirectory)
263 processEntry(entries[i]);
264 }
265
266 // Read remaining entries.
267 reader.readEntries(onSuccess, onError);
268 }.bind(this);
269
270 var reader = entry.createReader();
271 reader.readEntries(onSuccess, onError);
272 }.bind(this);
273
274 processEntry(this.entry_);
275 };
276
277 /**
278 * Scanner of the entries for the metadata seaerch on Drive File System.
279 * @param {string} query The query of the search.
280 * @param {DriveMetadataSearchContentScanner.SearchType} searchType The option
281 * of the search.
282 * @constructor
283 * @extends {ContentScanner}
284 */
285 function DriveMetadataSearchContentScanner(query, searchType) {
286 ContentScanner.call(this);
287 this.query_ = query;
288 this.searchType_ = searchType;
289 }
290
291 /**
292 * Extends ContentScanner.
293 */
294 DriveMetadataSearchContentScanner.prototype.__proto__ =
295 ContentScanner.prototype;
296
297 /**
298 * The search types on the Drive File System.
299 * @enum {string}
300 */
301 DriveMetadataSearchContentScanner.SearchType = Object.freeze({
302 SEARCH_ALL: 'ALL',
303 SEARCH_SHARED_WITH_ME: 'SHARED_WITH_ME',
304 SEARCH_RECENT_FILES: 'EXCLUDE_DIRECTORIES',
305 SEARCH_OFFLINE: 'OFFLINE'
306 });
307
308 /**
309 * Starts to metadata-search on Drive File System.
310 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
311 * entries are read. This can be called a couple of times until the
312 * completion.
313 * @param {function()} successCallback Called when the scan is completed
314 * successfully.
315 * @param {function(FileError)} errorCallback Called an error occurs.
316 * @override
317 */
318 DriveMetadataSearchContentScanner.prototype.scan = function(
319 entriesCallback, successCallback, errorCallback) {
320 chrome.fileBrowserPrivate.searchDriveMetadata(
321 {query: this.query_, types: this.searchType_, maxResults: 500},
322 function(results) {
323 if (this.cancelled_) {
324 errorCallback(util.createFileError(FileError.ABORT_ERR));
325 return;
326 }
327
328 if (!results) {
329 console.error('Drive search encountered an error.');
330 errorCallback(util.createFileError(
331 FileError.INVALID_MODIFICATION_ERR));
332 return;
333 }
334
335 var entries = results.map(function(result) { return result.entry; });
336 if (entries.length > 0)
337 entriesCallback(entries);
338 successCallback();
339 }.bind(this));
340 };
341
342 /**
8 * This class manages filters and determines a file should be shown or not. 343 * This class manages filters and determines a file should be shown or not.
9 * When filters are changed, a 'changed' event is fired. 344 * When filters are changed, a 'changed' event is fired.
10 * 345 *
11 * @param {MetadataCache} metadataCache Metadata cache service. 346 * @param {MetadataCache} metadataCache Metadata cache service.
12 * @param {boolean} showHidden If files starting with '.' are shown. 347 * @param {boolean} showHidden If files starting with '.' are shown.
13 * @constructor 348 * @constructor
14 * @extends {cr.EventTarget} 349 * @extends {cr.EventTarget}
15 */ 350 */
16 function FileFilter(metadataCache, showHidden) { 351 function FileFilter(metadataCache, showHidden) {
17 /** 352 /**
18 * @type {MetadataCache} 353 * @type {MetadataCache}
19 * @private 354 * @private
20 */ 355 */
21 this.metadataCache_ = metadataCache; 356 this.metadataCache_ = metadataCache;
357
22 /** 358 /**
23 * @type Object.<string, Function> 359 * @type Object.<string, Function>
24 * @private 360 * @private
25 */ 361 */
26 this.filters_ = {}; 362 this.filters_ = {};
27 this.setFilterHidden(!showHidden); 363 this.setFilterHidden(!showHidden);
28 364
29 // Do not show entries marked as 'deleted'. 365 // Do not show entries marked as 'deleted'.
30 this.addFilter('deleted', function(entry) { 366 this.addFilter('deleted', function(entry) {
31 var internal = this.metadataCache_.getCached(entry, 'internal'); 367 var internal = this.metadataCache_.getCached(entry, 'internal');
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 * 431 *
96 * @param {FileFilter} fileFilter The file-filter context. 432 * @param {FileFilter} fileFilter The file-filter context.
97 * @param {MetadataCache} metadataCache Metadata cache service. 433 * @param {MetadataCache} metadataCache Metadata cache service.
98 * @constructor 434 * @constructor
99 */ 435 */
100 function FileListContext(fileFilter, metadataCache) { 436 function FileListContext(fileFilter, metadataCache) {
101 /** 437 /**
102 * @type {cr.ui.ArrayDataModel} 438 * @type {cr.ui.ArrayDataModel}
103 */ 439 */
104 this.fileList = new cr.ui.ArrayDataModel([]); 440 this.fileList = new cr.ui.ArrayDataModel([]);
441
105 /** 442 /**
106 * @type {MetadataCache} 443 * @type {MetadataCache}
107 */ 444 */
108 this.metadataCache = metadataCache; 445 this.metadataCache = metadataCache;
109 446
110 /** 447 /**
111 * @type {FileFilter} 448 * @type {FileFilter}
112 */ 449 */
113 this.fileFilter = fileFilter; 450 this.fileFilter = fileFilter;
114 } 451 }
115 452
116 /** 453 /**
117 * This class is responsible for scanning directory (or search results), 454 * This class is responsible for scanning directory (or search results),
118 * and filling the fileList. Different descendants handle various types of 455 * and filling the fileList. Different descendants handle various types of
119 * directory contents shown: basic directory, drive search results, local search 456 * directory contents shown: basic directory, drive search results, local search
120 * results. 457 * results.
121 * @param {FileListContext} context The file list context. 458 * @param {FileListContext} context The file list context.
122 * @constructor 459 * @constructor
123 * @extends {cr.EventTarget} 460 * @extends {cr.EventTarget}
124 */ 461 */
125 function DirectoryContents(context) { 462 function DirectoryContents(context) {
126 this.context_ = context; 463 this.context_ = context;
127 this.fileList_ = context.fileList; 464 this.fileList_ = context.fileList;
128 this.scanCompletedCallback_ = null; 465 this.prefetchMetadataQueue_ = new AsyncUtil.Queue();
129 this.scanFailedCallback_ = null;
130 this.scanCancelled_ = false; 466 this.scanCancelled_ = false;
131 this.allChunksFetched_ = false;
132 this.pendingMetadataRequests_ = 0;
133 this.fileList_.prepareSort = this.prepareSort_.bind(this); 467 this.fileList_.prepareSort = this.prepareSort_.bind(this);
134 } 468 }
135 469
136 /** 470 /**
137 * DirectoryContents extends cr.EventTarget. 471 * DirectoryContents extends cr.EventTarget.
138 */ 472 */
139 DirectoryContents.prototype.__proto__ = cr.EventTarget.prototype; 473 DirectoryContents.prototype.__proto__ = cr.EventTarget.prototype;
140 474
141 /** 475 /**
142 * Create the copy of the object, but without scan started. 476 * Create the copy of the object, but without scan started.
(...skipping 23 matching lines...) Expand all
166 spliceArgs.unshift(0, fileList.length); 500 spliceArgs.unshift(0, fileList.length);
167 fileList.splice.apply(fileList, spliceArgs); 501 fileList.splice.apply(fileList, spliceArgs);
168 this.fileList_ = fileList; 502 this.fileList_ = fileList;
169 } 503 }
170 }; 504 };
171 505
172 /** 506 /**
173 * @return {boolean} If the scan is active. 507 * @return {boolean} If the scan is active.
174 */ 508 */
175 DirectoryContents.prototype.isScanning = function() { 509 DirectoryContents.prototype.isScanning = function() {
176 return !this.scanCancelled_ && 510 return this.scanner_ || this.prefetchMetadataQueue_.isRunning();
177 (!this.allChunksFetched_ || this.pendingMetadataRequests_ > 0);
178 }; 511 };
179 512
180 /** 513 /**
181 * @return {boolean} True if search results (drive or local). 514 * @return {boolean} True if search results (drive or local).
182 */ 515 */
183 DirectoryContents.prototype.isSearch = function() { 516 DirectoryContents.prototype.isSearch = function() {
184 return false; 517 return false;
185 }; 518 };
186 519
187 /** 520 /**
(...skipping 13 matching lines...) Expand all
201 534
202 /** 535 /**
203 * Start directory scan/search operation. Either 'scan-completed' or 536 * Start directory scan/search operation. Either 'scan-completed' or
204 * 'scan-failed' event will be fired upon completion. 537 * 'scan-failed' event will be fired upon completion.
205 */ 538 */
206 DirectoryContents.prototype.scan = function() { 539 DirectoryContents.prototype.scan = function() {
207 throw 'Not implemented.'; 540 throw 'Not implemented.';
208 }; 541 };
209 542
210 /** 543 /**
211 * Read next chunk of results from DirectoryReader. 544 * Cancels the running scan.
212 * @protected
213 */
214 DirectoryContents.prototype.readNextChunk = function() {
215 throw 'Not implemented.';
216 };
217
218 /**
219 * Cancel the running scan.
220 */ 545 */
221 DirectoryContents.prototype.cancelScan = function() { 546 DirectoryContents.prototype.cancelScan = function() {
222 if (this.scanCancelled_) 547 if (this.scanCancelled_)
223 return; 548 return;
224 this.scanCancelled_ = true; 549 this.scanCancelled_ = true;
550 if (this.scanner_)
551 this.scanner_.cancel();
552
553 this.prefetchMetadataQueue_.cancel();
225 cr.dispatchSimpleEvent(this, 'scan-cancelled'); 554 cr.dispatchSimpleEvent(this, 'scan-cancelled');
226 }; 555 };
227 556
557 /**
558 * Called when the scanning by scanner_ is done.
559 * @protected
560 */
561 DirectoryContents.prototype.onScanCompleted = function() {
562 this.scanner_ = null;
563 if (this.scanCancelled_)
564 return;
565
566 this.prefetchMetadataQueue_.run(function(callback) {
567 cr.dispatchSimpleEvent(this, 'scan-completed');
568 if (!this.isSearch() &&
569 this.getDirectoryEntry().fullPath === RootDirectory.DOWNLOADS)
570 metrics.recordMediumCount('DownloadsCount', this.fileList_.length);
571 callback();
572 }.bind(this));
573 };
228 574
229 /** 575 /**
230 * Called in case scan has failed. Should send the event. 576 * Called in case scan has failed. Should send the event.
231 * @protected 577 * @protected
232 */ 578 */
233 DirectoryContents.prototype.onError = function() { 579 DirectoryContents.prototype.onScanError = function() {
234 cr.dispatchSimpleEvent(this, 'scan-failed'); 580 this.scanner_ = null;
581 if (this.scanCancelled_)
582 return;
583
584 this.prefetchMetadataQueue_.run(function(callback) {
585 cr.dispatchSimpleEvent(this, 'scan-failed');
586 callback();
587 }.bind(this));
235 }; 588 };
236 589
237 /** 590 /**
238 * Called in case scan has completed succesfully. Should send the event. 591 * Called when some chunk of entries are read by scanner.
592 * @param {Array.<Entry>} entries The list of the scanned entries.
239 * @protected 593 * @protected
240 */ 594 */
241 DirectoryContents.prototype.lastChunkReceived = function() { 595 DirectoryContents.prototype.onNewEntries = function(entries) {
242 this.allChunksFetched_ = true; 596 if (this.scanCancelled_)
243 if (!this.scanCancelled_ && this.pendingMetadataRequests_ === 0) 597 return;
244 cr.dispatchSimpleEvent(this, 'scan-completed'); 598
599 var entriesFiltered = [].filter.call(
600 entries, this.context_.fileFilter.filter.bind(this.context_.fileFilter));
601
602 // Because the prefetchMetadata can be slow, throttling by splitting entries
603 // into smaller chunks to reduce UI latency.
604 // TODO(hidehiko,mtomasz): This should be handled in MetadataCache.
605 var MAX_CHUNK_SIZE = 50;
606 for (var i = 0; i < entriesFiltered.length; i += MAX_CHUNK_SIZE) {
607 var chunk = entriesFiltered.slice(i, i + MAX_CHUNK_SIZE);
608 this.prefetchMetadataQueue_.run(function(chunk, callback) {
609 this.prefetchMetadata(chunk, function() {
610 if (this.scanCancelled_) {
611 // Do nothing if the scanning is cancelled.
612 callback();
613 return;
614 }
615
616 this.fileList_.push.apply(this.fileList_, chunk);
617 cr.dispatchSimpleEvent(this, 'scan-updated');
618 callback();
619 }.bind(this));
620 }.bind(this, chunk));
621 }
245 }; 622 };
246 623
247 /** 624 /**
248 * Cache necessary data before a sort happens. 625 * Cache necessary data before a sort happens.
249 * 626 *
250 * This is called by the table code before a sort happens, so that we can 627 * This is called by the table code before a sort happens, so that we can
251 * go fetch data for the sort field that we may not have yet. 628 * go fetch data for the sort field that we may not have yet.
252 * @param {string} field Sort field. 629 * @param {string} field Sort field.
253 * @param {function(Object)} callback Called when done. 630 * @param {function(Object)} callback Called when done.
254 * @private 631 * @private
(...skipping 13 matching lines...) Expand all
268 /** 645 /**
269 * @param {Array.<Entry>} entries Files. 646 * @param {Array.<Entry>} entries Files.
270 * @param {function(Object)} callback Callback on done. 647 * @param {function(Object)} callback Callback on done.
271 */ 648 */
272 DirectoryContents.prototype.reloadMetadata = function(entries, callback) { 649 DirectoryContents.prototype.reloadMetadata = function(entries, callback) {
273 this.context_.metadataCache.clear(entries, '*'); 650 this.context_.metadataCache.clear(entries, '*');
274 this.context_.metadataCache.get(entries, 'filesystem', callback); 651 this.context_.metadataCache.get(entries, 'filesystem', callback);
275 }; 652 };
276 653
277 /** 654 /**
278 * @protected
279 * @param {Array.<Entry>} entries File list.
280 */
281 DirectoryContents.prototype.onNewEntries = function(entries) {
282 if (this.scanCancelled_)
283 return;
284
285 var entriesFiltered = [].filter.call(
286 entries, this.context_.fileFilter.filter.bind(this.context_.fileFilter));
287
288 var onPrefetched = function() {
289 this.pendingMetadataRequests_--;
290 if (this.scanCancelled_)
291 return;
292 this.fileList_.push.apply(this.fileList_, entriesFiltered);
293
294 if (this.pendingMetadataRequests_ === 0 && this.allChunksFetched_)
295 cr.dispatchSimpleEvent(this, 'scan-completed');
296 else
297 cr.dispatchSimpleEvent(this, 'scan-updated');
298
299 if (!this.allChunksFetched_)
300 this.readNextChunk();
301 };
302
303 this.pendingMetadataRequests_++;
304 this.prefetchMetadata(entriesFiltered, onPrefetched.bind(this));
305 };
306
307 /**
308 * @param {string} name Directory name. 655 * @param {string} name Directory name.
309 * @param {function(DirectoryEntry)} successCallback Called on success. 656 * @param {function(DirectoryEntry)} successCallback Called on success.
310 * @param {function(FileError)} errorCallback On error. 657 * @param {function(FileError)} errorCallback On error.
311 */ 658 */
312 DirectoryContents.prototype.createDirectory = function( 659 DirectoryContents.prototype.createDirectory = function(
313 name, successCallback, errorCallback) { 660 name, successCallback, errorCallback) {
314 throw 'Not implemented.'; 661 throw 'Not implemented.';
315 }; 662 };
316 663
317 664
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 * @return {DirectoryEntry} DirectoryEntry for the currnet entry. 697 * @return {DirectoryEntry} DirectoryEntry for the currnet entry.
351 */ 698 */
352 DirectoryContentsBasic.prototype.getLastNonSearchDirectoryEntry = function() { 699 DirectoryContentsBasic.prototype.getLastNonSearchDirectoryEntry = function() {
353 return this.entry_; 700 return this.entry_;
354 }; 701 };
355 702
356 /** 703 /**
357 * Start directory scan. 704 * Start directory scan.
358 */ 705 */
359 DirectoryContentsBasic.prototype.scan = function() { 706 DirectoryContentsBasic.prototype.scan = function() {
360 if (!this.entry_ || this.entry_ === DirectoryModel.fakeDriveEntry_) { 707 // TODO: this scan method must be called at most once. Remove such a
mtomasz 2013/10/01 05:39:38 nit: We use syntax: TODO(login): Sentence from upp
hidehiko 2013/10/01 05:54:03 Thank you for your offer. Added your and my names.
361 this.lastChunkReceived(); 708 // limitation.
362 return; 709 this.scanner_ = new DirectoryContentScanner(this.entry_);
363 } 710 this.scanner_.scan(this.onNewEntries.bind(this),
364 711 this.onScanCompleted.bind(this),
365 metrics.startInterval('DirectoryScan'); 712 this.onScanError.bind(this));
366 this.reader_ = this.entry_.createReader();
367 this.readNextChunk();
368 }; 713 };
369 714
370 /** 715 /**
371 * Read next chunk of results from DirectoryReader.
372 * @protected
373 */
374 DirectoryContentsBasic.prototype.readNextChunk = function() {
375 this.reader_.readEntries(this.onChunkComplete_.bind(this),
376 this.onError.bind(this));
377 };
378
379 /**
380 * @param {Array.<Entry>} entries File list.
381 * @private
382 */
383 DirectoryContentsBasic.prototype.onChunkComplete_ = function(entries) {
384 if (this.scanCancelled_)
385 return;
386
387 if (entries.length == 0) {
388 this.lastChunkReceived();
389 this.recordMetrics_();
390 return;
391 }
392
393 this.onNewEntries(entries);
394 };
395
396 /**
397 * @private
398 */
399 DirectoryContentsBasic.prototype.recordMetrics_ = function() {
400 metrics.recordInterval('DirectoryScan');
401 if (this.entry_.fullPath === RootDirectory.DOWNLOADS)
402 metrics.recordMediumCount('DownloadsCount', this.fileList_.length);
403 };
404
405 /**
406 * @param {string} name Directory name. 716 * @param {string} name Directory name.
407 * @param {function(Entry)} successCallback Called on success. 717 * @param {function(Entry)} successCallback Called on success.
408 * @param {function(FileError)} errorCallback On error. 718 * @param {function(FileError)} errorCallback On error.
409 */ 719 */
410 DirectoryContentsBasic.prototype.createDirectory = function( 720 DirectoryContentsBasic.prototype.createDirectory = function(
411 name, successCallback, errorCallback) { 721 name, successCallback, errorCallback) {
722 // TODO(hidehiko): createDirectory should not be the part of
723 // DirectoryContent.
412 if (!this.entry_) { 724 if (!this.entry_) {
413 errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR)); 725 errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR));
414 return; 726 return;
415 } 727 }
416 728
417 var onSuccess = function(newEntry) { 729 var onSuccess = function(newEntry) {
418 this.reloadMetadata([newEntry], function() { 730 this.reloadMetadata([newEntry], function() {
419 successCallback(newEntry); 731 successCallback(newEntry);
420 }); 732 });
421 }; 733 };
422 734
423 this.entry_.getDirectory(name, {create: true, exclusive: true}, 735 this.entry_.getDirectory(name, {create: true, exclusive: true},
424 onSuccess.bind(this), errorCallback); 736 onSuccess.bind(this), errorCallback);
425 }; 737 };
426 738
427 /** 739 /**
428 * Delay to be used for drive search scan.
429 * The goal is to reduce the number of server requests when user is typing the
430 * query.
431 *
432 * @type {number}
433 * @const
434 */
435 DirectoryContentsDriveSearch.SCAN_DELAY = 200;
436
437 /**
438 * Maximum number of results which is shown on the search.
439 *
440 * @type {number}
441 * @const
442 */
443 DirectoryContentsDriveSearch.MAX_RESULTS = 100;
444
445 /**
446 * @param {FileListContext} context File list context. 740 * @param {FileListContext} context File list context.
447 * @param {DirectoryEntry} dirEntry Current directory. 741 * @param {DirectoryEntry} dirEntry Current directory.
448 * @param {DirectoryEntry} previousDirEntry DirectoryEntry that was current 742 * @param {DirectoryEntry} previousDirEntry DirectoryEntry that was current
449 * before the search. 743 * before the search.
450 * @param {string} query Search query. 744 * @param {string} query Search query.
451 * @constructor 745 * @constructor
452 * @extends {DirectoryContents} 746 * @extends {DirectoryContents}
453 */ 747 */
454 function DirectoryContentsDriveSearch(context, 748 function DirectoryContentsDriveSearch(context,
455 dirEntry, 749 dirEntry,
456 previousDirEntry, 750 previousDirEntry,
457 query) { 751 query) {
458 DirectoryContents.call(this, context); 752 DirectoryContents.call(this, context);
459 this.directoryEntry_ = dirEntry; 753 this.directoryEntry_ = dirEntry;
460 this.previousDirectoryEntry_ = previousDirEntry; 754 this.previousDirectoryEntry_ = previousDirEntry;
461 this.query_ = query; 755 this.query_ = query;
462 this.nextFeed_ = '';
463 this.done_ = false;
464 this.fetchedResultsNum_ = 0;
465 } 756 }
466 757
467 /** 758 /**
468 * Extends DirectoryContents. 759 * Extends DirectoryContents.
469 */ 760 */
470 DirectoryContentsDriveSearch.prototype.__proto__ = DirectoryContents.prototype; 761 DirectoryContentsDriveSearch.prototype.__proto__ = DirectoryContents.prototype;
471 762
472 /** 763 /**
473 * Create the copy of the object, but without scan started. 764 * Create the copy of the object, but without scan started.
474 * @return {DirectoryContentsBasic} Object copy. 765 * @return {DirectoryContentsBasic} Object copy.
(...skipping 25 matching lines...) Expand all
500 */ 791 */
501 DirectoryContentsDriveSearch.prototype.getLastNonSearchDirectoryEntry = 792 DirectoryContentsDriveSearch.prototype.getLastNonSearchDirectoryEntry =
502 function() { 793 function() {
503 return this.previousDirectoryEntry_; 794 return this.previousDirectoryEntry_;
504 }; 795 };
505 796
506 /** 797 /**
507 * Start directory scan. 798 * Start directory scan.
508 */ 799 */
509 DirectoryContentsDriveSearch.prototype.scan = function() { 800 DirectoryContentsDriveSearch.prototype.scan = function() {
510 // Let's give another search a chance to cancel us before we begin. 801 this.scanner_ = new DriveSearchContentScanner(this.query_);
511 setTimeout(this.readNextChunk.bind(this), 802 this.scanner_.scan(this.onNewEntries.bind(this),
512 DirectoryContentsDriveSearch.SCAN_DELAY); 803 this.onScanCompleted.bind(this),
804 this.onScanError.bind(this));
513 }; 805 };
514 806
515 /** 807 /**
516 * All the results are read in one chunk, so when we try to read second chunk,
517 * it means we're done.
518 */
519 DirectoryContentsDriveSearch.prototype.readNextChunk = function() {
520 if (this.scanCancelled_)
521 return;
522
523 if (this.done_) {
524 this.lastChunkReceived();
525 return;
526 }
527
528 var searchCallback = (function(entries, nextFeed) {
529 // TODO(tbarzic): Improve error handling.
530 if (!entries) {
531 console.error('Drive search encountered an error.');
532 this.lastChunkReceived();
533 return;
534 }
535 this.nextFeed_ = nextFeed;
536 var remaining =
537 DirectoryContentsDriveSearch.MAX_RESULTS - this.fetchedResultsNum_;
538 if (entries.length >= remaining) {
539 entries = entries.slice(0, remaining);
540 this.nextFeed_ = '';
541 }
542 this.fetchedResultsNum_ += entries.length;
543
544 this.done_ = (this.nextFeed_ == '');
545
546 this.onNewEntries(entries);
547 }).bind(this);
548
549 var searchParams = {
550 'query': this.query_,
551 'nextFeed': this.nextFeed_
552 };
553 chrome.fileBrowserPrivate.searchDrive(searchParams, searchCallback);
554 };
555
556
557 /**
558 * @param {FileListContext} context File list context. 808 * @param {FileListContext} context File list context.
559 * @param {DirectoryEntry} dirEntry Current directory. 809 * @param {DirectoryEntry} dirEntry Current directory.
560 * @param {string} query Search query. 810 * @param {string} query Search query.
561 * @constructor 811 * @constructor
562 * @extends {DirectoryContents} 812 * @extends {DirectoryContents}
563 */ 813 */
564 function DirectoryContentsLocalSearch(context, dirEntry, query) { 814 function DirectoryContentsLocalSearch(context, dirEntry, query) {
565 DirectoryContents.call(this, context); 815 DirectoryContents.call(this, context);
566 this.directoryEntry_ = dirEntry; 816 this.directoryEntry_ = dirEntry;
567 this.query_ = query.toLowerCase(); 817 this.query_ = query;
568 } 818 }
569 819
570 /** 820 /**
571 * Extends DirectoryContents 821 * Extends DirectoryContents
572 */ 822 */
573 DirectoryContentsLocalSearch.prototype.__proto__ = DirectoryContents.prototype; 823 DirectoryContentsLocalSearch.prototype.__proto__ = DirectoryContents.prototype;
574 824
575 /** 825 /**
576 * Create the copy of the object, but without scan started. 826 * Create the copy of the object, but without scan started.
577 * @return {DirectoryContentsBasic} Object copy. 827 * @return {DirectoryContentsBasic} Object copy.
(...skipping 25 matching lines...) Expand all
603 DirectoryContentsLocalSearch.prototype.getLastNonSearchDirectoryEntry = 853 DirectoryContentsLocalSearch.prototype.getLastNonSearchDirectoryEntry =
604 function() { 854 function() {
605 return this.directoryEntry_; 855 return this.directoryEntry_;
606 }; 856 };
607 857
608 /** 858 /**
609 * Start directory scan/search operation. Either 'scan-completed' or 859 * Start directory scan/search operation. Either 'scan-completed' or
610 * 'scan-failed' event will be fired upon completion. 860 * 'scan-failed' event will be fired upon completion.
611 */ 861 */
612 DirectoryContentsLocalSearch.prototype.scan = function() { 862 DirectoryContentsLocalSearch.prototype.scan = function() {
613 this.pendingScans_ = 0; 863 this.scanner_ =
614 this.scanDirectory_(this.directoryEntry_); 864 new LocalSearchContentScanner(this.directoryEntry_, this.query_);
865 this.scanner_.scan(this.onNewEntries.bind(this),
866 this.onScanCompleted.bind(this),
867 this.onScanError.bind(this));
615 }; 868 };
616 869
617 /** 870 /**
618 * Scan a directory.
619 * @param {DirectoryEntry} entry A directory to scan.
620 * @private
621 */
622 DirectoryContentsLocalSearch.prototype.scanDirectory_ = function(entry) {
623 this.pendingScans_++;
624 var reader = entry.createReader();
625 var found = [];
626
627 var onChunkComplete = function(entries) {
628 if (this.scanCancelled_)
629 return;
630
631 if (entries.length === 0) {
632 if (found.length > 0)
633 this.onNewEntries(found);
634 this.pendingScans_--;
635 if (this.pendingScans_ === 0)
636 this.lastChunkReceived();
637 return;
638 }
639
640 for (var i = 0; i < entries.length; i++) {
641 if (entries[i].name.toLowerCase().indexOf(this.query_) != -1) {
642 found.push(entries[i]);
643 }
644
645 if (entries[i].isDirectory)
646 this.scanDirectory_(entries[i]);
647 }
648
649 getNextChunk();
650 }.bind(this);
651
652 var getNextChunk = function() {
653 reader.readEntries(onChunkComplete, this.onError.bind(this));
654 }.bind(this);
655
656 getNextChunk();
657 };
658
659 /**
660 * We get results for each directory in one go in scanDirectory_.
661 */
662 DirectoryContentsLocalSearch.prototype.readNextChunk = function() {
663 };
664
665 /**
666 * List of search types for DirectoryContentsDriveSearch.
667 * TODO(haruki): SHARED_WITH_ME support for searchDriveMetadata is not yet
668 * implemented. Update this when it's done.
669 * SEARCH_ALL uses no filtering.
670 * SEARCH_SHARED_WITH_ME searches for the shared-with-me entries.
671 * SEARCH_RECENT_FILES searches for recently accessed file entries.
672 * @enum {number}
673 */
674 DirectoryContentsDriveSearchMetadata.SearchType = {
675 SEARCH_ALL: 0,
676 SEARCH_SHARED_WITH_ME: 1,
677 SEARCH_RECENT_FILES: 2,
678 SEARCH_OFFLINE: 3
679 };
680
681 /**
682 * DirectoryContents to list Drive files using searchDriveMetadata(). 871 * DirectoryContents to list Drive files using searchDriveMetadata().
683 * 872 *
684 * @param {FileListContext} context File list context. 873 * @param {FileListContext} context File list context.
685 * @param {DirectoryEntry} driveDirEntry Directory for actual Drive. 874 * @param {DirectoryEntry} driveDirEntry Directory for actual Drive.
686 * @param {DirectoryEntry} fakeDirEntry Fake directory representing the set of 875 * @param {DirectoryEntry} fakeDirEntry Fake directory representing the set of
687 * result files. This serves as a top directory for this search. 876 * result files. This serves as a top directory for this search.
688 * @param {string} query Search query to filter the files. 877 * @param {string} query Search query to filter the files.
689 * @param {DirectoryContentsDriveSearchMetadata.SearchType} searchType 878 * @param {DriveMetadataSearchContentScanner.SearchType} searchType
690 * Type of search. searchDriveMetadata will restricts the entries based on 879 * Type of search. searchDriveMetadata will restricts the entries based on
691 * the given search type. 880 * the given search type.
692 * @constructor 881 * @constructor
693 * @extends {DirectoryContents} 882 * @extends {DirectoryContents}
694 */ 883 */
695 function DirectoryContentsDriveSearchMetadata(context, 884 function DirectoryContentsDriveSearchMetadata(context,
696 driveDirEntry, 885 driveDirEntry,
697 fakeDirEntry, 886 fakeDirEntry,
698 query, 887 query,
699 searchType) { 888 searchType) {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
742 DirectoryContentsDriveSearchMetadata.prototype.getLastNonSearchDirectoryEntry = 931 DirectoryContentsDriveSearchMetadata.prototype.getLastNonSearchDirectoryEntry =
743 function() { 932 function() {
744 return this.driveDirEntry_; 933 return this.driveDirEntry_;
745 }; 934 };
746 935
747 /** 936 /**
748 * Start directory scan/search operation. Either 'scan-completed' or 937 * Start directory scan/search operation. Either 'scan-completed' or
749 * 'scan-failed' event will be fired upon completion. 938 * 'scan-failed' event will be fired upon completion.
750 */ 939 */
751 DirectoryContentsDriveSearchMetadata.prototype.scan = function() { 940 DirectoryContentsDriveSearchMetadata.prototype.scan = function() {
752 this.readNextChunk(); 941 this.scanner_ =
942 new DriveMetadataSearchContentScanner(this.query_, this.searchType_);
943 this.scanner_.scan(this.onNewEntries.bind(this),
944 this.onScanCompleted.bind(this),
945 this.onScanError.bind(this));
753 }; 946 };
754
755 /**
756 * All the results are read in one chunk, so when we try to read second chunk,
757 * it means we're done.
758 */
759 DirectoryContentsDriveSearchMetadata.prototype.readNextChunk = function() {
760 if (this.scanCancelled_)
761 return;
762
763 if (this.done_) {
764 this.lastChunkReceived();
765 return;
766 }
767
768 var searchCallback = (function(results, nextFeed) {
769 if (!results) {
770 console.error('Drive search encountered an error.');
771 this.lastChunkReceived();
772 return;
773 }
774 this.done_ = true;
775
776 var entries = results.map(function(r) { return r.entry; });
777 this.onNewEntries(entries);
778 this.lastChunkReceived();
779 }).bind(this);
780
781 var type;
782 switch (this.searchType_) {
783 case DirectoryContentsDriveSearchMetadata.SearchType.SEARCH_ALL:
784 type = 'ALL';
785 break;
786 case DirectoryContentsDriveSearchMetadata.SearchType.SEARCH_SHARED_WITH_ME:
787 type = 'SHARED_WITH_ME';
788 break;
789 case DirectoryContentsDriveSearchMetadata.SearchType.SEARCH_RECENT_FILES:
790 type = 'EXCLUDE_DIRECTORIES';
791 break;
792 case DirectoryContentsDriveSearchMetadata.SearchType.SEARCH_OFFLINE:
793 type = 'OFFLINE';
794 break;
795 default:
796 throw Error('Unknown search type: ' + this.searchType_);
797 }
798 var searchParams = {
799 'query': this.query_,
800 'types': type,
801 'maxResults': 500
802 };
803 chrome.fileBrowserPrivate.searchDriveMetadata(searchParams, searchCallback);
804 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/async_util.js ('k') | chrome/browser/resources/file_manager/js/directory_model.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698