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

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.
mtomasz 2013/10/01 02:13:44 @constructor is missing
hidehiko 2013/10/01 04:12:23 Done.
9 */
10 function ContentScanner() {
11 this.cancelled_ = false;
12 }
13
14 /**
15 * Starts to scan the entries. For example, starts to read the entries in a
16 * directory, or starts to search with some query on a file system.
mtomasz 2013/10/01 02:13:44 nit: Please add a new line after the description i
hidehiko 2013/10/01 04:12:23 Done.
17 * Derived classes must override this method.
18 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
19 * entries are read. This can be called a couple of times until the
20 * completion.
21 * @param {function()} successCallback Called when the scan is completed
22 * successfully.
23 * @param {function(FileError)} errorCallback Called an error occurs.
24 */
25 ContentScanner.prototype.startScan = function(
26 entriesCallback, successCallback, errorCallback) {
27 };
28
29 /**
30 * Request cancelling of the running scan. When the cancelling is done,
31 * an error will be reported from errorCallback passed to startScan().
32 */
33 ContentScanner.prototype.cancel = function() {
34 this.cancelled_ = true;
35 };
36
37 /**
38 * Scanner of the entries in a directory.
39 * @param {DirectoryEntry} entry The directory to be read.
40 * @extends {ContentScanner}
mtomasz 2013/10/01 02:13:44 @constructor is missing
hidehiko 2013/10/01 04:12:23 Done.
41 */
42 function DirectoryContentScanner(entry) {
43 ContentScanner.call(this);
44 this.entry_ = entry;
45 }
46
47 /**
48 * Extends ContentScanner.
49 */
50 DirectoryContentScanner.prototype.__proto__ = ContentScanner.prototype;
51
52 /**
53 * Starts to read the entries in the directory.
54 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
55 * entries are read. This can be called a couple of times until the
56 * completion.
57 * @param {function()} successCallback Called when the scan is completed
58 * successfully.
59 * @param {function(FileError)} errorCallback Called an error occurs.
mtomasz 2013/10/01 02:13:44 @override is missing
hidehiko 2013/10/01 04:12:23 Done.
60 */
61 DirectoryContentScanner.prototype.startScan = function(
62 entriesCallback, successCallback, errorCallback) {
63 if (!this.entry_ || this.entry_ === DirectoryModel.fakeDriveEntry_) {
64 // If entry is not specified or a fake, we cannot read it.
65 errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR));
66 return;
67 }
68
69 metrics.startInterval('DirectoryScan');
70 var reader = this.entry_.createReader();
71 var readEntries = function() {
72 reader.readEntries(
73 function(entries) {
74 if (this.cancelled_) {
75 errorCallback(util.createFileError(FileError.ABORT_ERR));
76 return;
77 }
78
79 if (entries.length === 0) {
80 // All entries are read.
81 metrics.recordInterval('DirectoryScan');
82 successCallback();
83 return;
84 }
85
86 entriesCallback(entries);
87 readEntries();
88 }.bind(this),
89 errorCallback);
90 }.bind(this);
91 readEntries();
92 };
93
94 /**
95 * Scanner of the entries for the search results on Drive File System.
96 * @param {string} query The query string.
97 * @extends {ContentScanner}
mtomasz 2013/10/01 02:13:44 ditto (@constructor)
hidehiko 2013/10/01 04:12:23 Done.
98 */
99 function DriveSearchContentScanner(query) {
100 ContentScanner.call(this);
101 this.query_ = query;
102 }
103
104 /**
105 * Extends ContentScanner.
106 */
107 DriveSearchContentScanner.prototype.__proto__ = ContentScanner.prototype;
108
109 /**
110 * Delay to be used for drive search scan, in order to reduce the number of
111 * server requests while user is typeing the query.
mtomasz 2013/10/01 02:13:44 nit: typeing -> typing nit: in milliseconds (we al
hidehiko 2013/10/01 04:12:23 Done.
112 * @type {number}
113 * @private
114 * @const
115 */
116 DriveSearchContentScanner.SCAN_DELAY_ = 200;
117
118 /**
119 * Maximum number of results which is shown on the search.
120 * @type {number}
121 * @private
122 * @const
123 */
124 DriveSearchContentScanner.MAX_RESULTS_ = 100;
125
126 /**
127 * Starts to search on Drive File System.
128 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
129 * entries are read. This can be called a couple of times until the
130 * completion.
131 * @param {function()} successCallback Called when the scan is completed
132 * successfully.
133 * @param {function(FileError)} errorCallback Called an error occurs.
mtomasz 2013/10/01 02:13:44 nit: ditto (@override)
hidehiko 2013/10/01 04:12:23 Done.
134 */
135 DriveSearchContentScanner.prototype.startScan = function(
136 entriesCallback, successCallback, errorCallback) {
137 var numReadEntries = 0;
138 var readEntries = function(nextFeed) {
139 chrome.fileBrowserPrivate.searchDrive(
140 {query: this.query_, nextFeed: nextFeed},
141 function(entries, nextFeed) {
142 if (this.cancelled_) {
143 errorCallback(util.createFileError(FileError.ABORT_ERR));
144 return;
145 }
146
147 // TODO(tbarzic): Improve error handling.
148 if (!entries) {
149 console.error('Drive search encountered an error.');
150 errorCallback(util.createFileError(
151 FileError.INVALID_MODIFICATION_ERR));
152 return;
153 }
154
155 var numRemainingEntries =
156 DriveSearchContentScanner.MAX_RESULTS_ - numReadEntries;
157 if (entries.length >= numRemainingEntries) {
158 // The limit is hit, so quit the scan here.
159 entries = entries.slice(0, numRemainingEntries);
160 nextFeed = '';
161 }
162
163 numReadEntries += entries.length;
164 if (entries.length > 0)
165 entriesCallback(entries);
166
167 if (nextFeed === '')
168 successCallback();
169 else
170 readEntries(nextFeed);
171 }.bind(this));
172 }.bind(this);
173
174 // Let's give another search a chance to cancel us before we begin.
175 setTimeout(
176 function() {
177 // Check cancelled state before read the entries.
178 if (this.cancelled_) {
179 errorCallback(util.createFileError(FileError.ABORT_ERR));
180 return;
181 }
182 readEntries('');
183 }.bind(this),
184 DriveSearchContentScanner.SCAN_DELAY_);
185 };
186
187 /**
188 * Scanner of the entries of the file name search on the directory tree, whose
189 * root is entry.
190 * @param {DirectoryEntry} entry The root of the search target directory tree.
191 * @param {string} query The query of the search.
mtomasz 2013/10/01 02:13:44 ditto
hidehiko 2013/10/01 04:12:23 Done.
192 */
193 function LocalSearchContentScanner(entry, query) {
194 ContentScanner.call(this);
195 this.entry_ = entry;
196 this.query_ = query.toLowerCase();
197 }
198
199 /**
200 * Extedns ContentScanner.
201 */
202 LocalSearchContentScanner.prototype.__proto__ = ContentScanner.prototype;
203
204 /**
205 * Starts the file name search.
206 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
207 * entries are read. This can be called a couple of times until the
208 * completion.
209 * @param {function()} successCallback Called when the scan is completed
210 * successfully.
211 * @param {function(FileError)} errorCallback Called an error occurs.
mtomasz 2013/10/01 02:13:44 ditto here and in other places
hidehiko 2013/10/01 04:12:23 Done.
212 */
213 LocalSearchContentScanner.prototype.startScan = function(
214 entriesCallback, successCallback, errorCallback) {
215 var numRunningTasks = 0;
216 var error = null;
217 var maybeRunCallback = function() {
218 if (numRunningTasks === 0) {
219 if (this.cancelled_)
220 errorCallback(util.createFileError(FileError.ABORT_ERR));
221 else if (error)
222 errorCallback(error);
223 else
224 successCallback();
225 }
226 }.bind(this);
227
228 var processEntry = function(entry) {
229 numRunningTasks++;
230 var onError = function(fileError) {
231 if (!error)
232 error = fileError;
233 numRunningTasks--;
234 maybeRunCallback();
235 };
236
237 var onSuccess = function(entries) {
238 if (this.cancelled_ || error || entries.length === 0) {
239 numRunningTasks--;
240 maybeRunCallback();
241 return;
242 }
243
244 // Filters by the query, and if found, run entriesCallback.
245 var foundEntries = entries.filter(function(entry) {
246 return entry.name.toLowerCase().indexOf(this.query_) >= 0;
247 }.bind(this));
248 if (foundEntries.length > 0)
249 entriesCallback(foundEntries);
250
251 // Start to process sub directories.
252 for (var i = 0; i < entries.length; i++) {
253 if (entries[i].isDirectory)
254 processEntry(entries[i]);
255 }
256
257 // Read remaining entries.
258 reader.readEntries(onSuccess, onError);
259 }.bind(this);
260
261 var reader = entry.createReader();
262 reader.readEntries(onSuccess, onError);
263 }.bind(this);
264
265 processEntry(this.entry_);
266 };
267
268 /**
269 * Scanner of the entries for the metadata seaerch on Drive File System.
270 * @param {string} query The query of the search.
271 * @param {DriveMetadataSearchContentScanner.SearchType} searchType The option
272 * of the search.
273 * @extends {ContentScanner}
274 */
275 function DriveMetadataSearchContentScanner(query, searchType) {
276 ContentScanner.call(this);
277 this.query_ = query;
278 this.searchType_ = searchType;
279 }
280
281 /**
282 * Extends ContentScanner.
283 */
284 DriveMetadataSearchContentScanner.prototype.__proto__ =
285 ContentScanner.prototype;
286
287 /**
288 * The search types on the Drive File System.
mtomasz 2013/10/01 02:13:44 @enum {string} missing
hidehiko 2013/10/01 04:12:23 Done.
289 */
290 DriveMetadataSearchContentScanner.SearchType = Object.freeze({
291 SEARCH_ALL: 'ALL',
292 SEARCH_SHARED_WITH_ME: 'SHARED_WITH_ME',
293 SEARCH_RECENT_FILES: 'EXCLUDE_DIRECTORIES',
294 SEARCH_OFFLINE: 'OFFLINE'
295 });
296
297 /**
298 * Starts to metadata-search on Drive File System.
299 * @param {function(Array.<Entry>)} entriesCallback Called when some chunk of
300 * entries are read. This can be called a couple of times until the
301 * completion.
302 * @param {function()} successCallback Called when the scan is completed
303 * successfully.
304 * @param {function(FileError)} errorCallback Called an error occurs.
305 */
306 DriveMetadataSearchContentScanner.prototype.startScan = function(
mtomasz 2013/10/01 02:13:44 optional: how about just scan() instead of startSc
hidehiko 2013/10/01 04:12:23 Done.
307 entriesCallback, successCallback, errorCallback) {
308 chrome.fileBrowserPrivate.searchDriveMetadata(
309 {query: this.query_, types: this.searchType_, maxResults: 500},
310 function(results) {
311 if (this.cancelled_) {
312 errorCallback(util.createFileError(FileError.ABORT_ERR));
313 return;
314 }
315
316 if (!results) {
317 console.error('Drive search encountered an error.');
318 errorCallback(util.createFileError(
319 FileError.INVALID_MODIFICATION_ERR));
320 return;
321 }
322
323 var entries = results.map(function(result) { return result.entry; });
324 if (entries.length > 0)
325 entriesCallback(entries);
326 successCallback();
327 }.bind(this));
328 };
329
330 /**
8 * This class manages filters and determines a file should be shown or not. 331 * This class manages filters and determines a file should be shown or not.
9 * When filters are changed, a 'changed' event is fired. 332 * When filters are changed, a 'changed' event is fired.
10 * 333 *
11 * @param {MetadataCache} metadataCache Metadata cache service. 334 * @param {MetadataCache} metadataCache Metadata cache service.
12 * @param {boolean} showHidden If files starting with '.' are shown. 335 * @param {boolean} showHidden If files starting with '.' are shown.
13 * @constructor 336 * @constructor
14 * @extends {cr.EventTarget} 337 * @extends {cr.EventTarget}
15 */ 338 */
16 function FileFilter(metadataCache, showHidden) { 339 function FileFilter(metadataCache, showHidden) {
17 /** 340 /**
18 * @type {MetadataCache} 341 * @type {MetadataCache}
19 * @private 342 * @private
20 */ 343 */
21 this.metadataCache_ = metadataCache; 344 this.metadataCache_ = metadataCache;
345
22 /** 346 /**
23 * @type Object.<string, Function> 347 * @type Object.<string, Function>
24 * @private 348 * @private
25 */ 349 */
26 this.filters_ = {}; 350 this.filters_ = {};
27 this.setFilterHidden(!showHidden); 351 this.setFilterHidden(!showHidden);
28 352
29 // Do not show entries marked as 'deleted'. 353 // Do not show entries marked as 'deleted'.
30 this.addFilter('deleted', function(entry) { 354 this.addFilter('deleted', function(entry) {
31 var internal = this.metadataCache_.getCached(entry, 'internal'); 355 var internal = this.metadataCache_.getCached(entry, 'internal');
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 * 419 *
96 * @param {FileFilter} fileFilter The file-filter context. 420 * @param {FileFilter} fileFilter The file-filter context.
97 * @param {MetadataCache} metadataCache Metadata cache service. 421 * @param {MetadataCache} metadataCache Metadata cache service.
98 * @constructor 422 * @constructor
99 */ 423 */
100 function FileListContext(fileFilter, metadataCache) { 424 function FileListContext(fileFilter, metadataCache) {
101 /** 425 /**
102 * @type {cr.ui.ArrayDataModel} 426 * @type {cr.ui.ArrayDataModel}
103 */ 427 */
104 this.fileList = new cr.ui.ArrayDataModel([]); 428 this.fileList = new cr.ui.ArrayDataModel([]);
429
105 /** 430 /**
106 * @type {MetadataCache} 431 * @type {MetadataCache}
107 */ 432 */
108 this.metadataCache = metadataCache; 433 this.metadataCache = metadataCache;
109 434
110 /** 435 /**
111 * @type {FileFilter} 436 * @type {FileFilter}
112 */ 437 */
113 this.fileFilter = fileFilter; 438 this.fileFilter = fileFilter;
114 } 439 }
115 440
116 /** 441 /**
117 * This class is responsible for scanning directory (or search results), 442 * This class is responsible for scanning directory (or search results),
118 * and filling the fileList. Different descendants handle various types of 443 * and filling the fileList. Different descendants handle various types of
119 * directory contents shown: basic directory, drive search results, local search 444 * directory contents shown: basic directory, drive search results, local search
120 * results. 445 * results.
121 * @param {FileListContext} context The file list context. 446 * @param {FileListContext} context The file list context.
122 * @constructor 447 * @constructor
123 * @extends {cr.EventTarget} 448 * @extends {cr.EventTarget}
124 */ 449 */
125 function DirectoryContents(context) { 450 function DirectoryContents(context) {
126 this.context_ = context; 451 this.context_ = context;
127 this.fileList_ = context.fileList; 452 this.fileList_ = context.fileList;
128 this.scanCompletedCallback_ = null; 453 this.prefetchMetadataQueue_ = new AsyncUtil.Queue();
129 this.scanFailedCallback_ = null;
130 this.scanCancelled_ = false; 454 this.scanCancelled_ = false;
131 this.allChunksFetched_ = false;
132 this.pendingMetadataRequests_ = 0;
133 this.fileList_.prepareSort = this.prepareSort_.bind(this); 455 this.fileList_.prepareSort = this.prepareSort_.bind(this);
134 } 456 }
135 457
136 /** 458 /**
137 * DirectoryContents extends cr.EventTarget. 459 * DirectoryContents extends cr.EventTarget.
138 */ 460 */
139 DirectoryContents.prototype.__proto__ = cr.EventTarget.prototype; 461 DirectoryContents.prototype.__proto__ = cr.EventTarget.prototype;
140 462
141 /** 463 /**
142 * Create the copy of the object, but without scan started. 464 * Create the copy of the object, but without scan started.
(...skipping 23 matching lines...) Expand all
166 spliceArgs.unshift(0, fileList.length); 488 spliceArgs.unshift(0, fileList.length);
167 fileList.splice.apply(fileList, spliceArgs); 489 fileList.splice.apply(fileList, spliceArgs);
168 this.fileList_ = fileList; 490 this.fileList_ = fileList;
169 } 491 }
170 }; 492 };
171 493
172 /** 494 /**
173 * @return {boolean} If the scan is active. 495 * @return {boolean} If the scan is active.
174 */ 496 */
175 DirectoryContents.prototype.isScanning = function() { 497 DirectoryContents.prototype.isScanning = function() {
176 return !this.scanCancelled_ && 498 return this.scanner_ || this.prefetchMetadataQueue_.isRunning();
177 (!this.allChunksFetched_ || this.pendingMetadataRequests_ > 0);
178 }; 499 };
179 500
180 /** 501 /**
181 * @return {boolean} True if search results (drive or local). 502 * @return {boolean} True if search results (drive or local).
182 */ 503 */
183 DirectoryContents.prototype.isSearch = function() { 504 DirectoryContents.prototype.isSearch = function() {
184 return false; 505 return false;
185 }; 506 };
186 507
187 /** 508 /**
(...skipping 13 matching lines...) Expand all
201 522
202 /** 523 /**
203 * Start directory scan/search operation. Either 'scan-completed' or 524 * Start directory scan/search operation. Either 'scan-completed' or
204 * 'scan-failed' event will be fired upon completion. 525 * 'scan-failed' event will be fired upon completion.
205 */ 526 */
206 DirectoryContents.prototype.scan = function() { 527 DirectoryContents.prototype.scan = function() {
207 throw 'Not implemented.'; 528 throw 'Not implemented.';
208 }; 529 };
209 530
210 /** 531 /**
211 * Read next chunk of results from DirectoryReader. 532 * 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 */ 533 */
221 DirectoryContents.prototype.cancelScan = function() { 534 DirectoryContents.prototype.cancelScan = function() {
222 if (this.scanCancelled_) 535 if (this.scanCancelled_)
223 return; 536 return;
224 this.scanCancelled_ = true; 537 this.scanCancelled_ = true;
538 if (this.scanner_)
539 this.scanner_.cancel();
540
541 this.prefetchMetadataQueue_.cancel();
225 cr.dispatchSimpleEvent(this, 'scan-cancelled'); 542 cr.dispatchSimpleEvent(this, 'scan-cancelled');
226 }; 543 };
227 544
545 /**
546 * Called when the scanning by scanner_ is done.
547 * @protected
548 */
549 DirectoryContents.prototype.onScanCompleted = function() {
550 this.scanner_ = null;
551 if (this.scanCancelled_)
552 return;
553
554 this.prefetchMetadataQueue_.run(function(callback) {
555 cr.dispatchSimpleEvent(this, 'scan-completed');
556 if (!this.isSearch() &&
557 this.getDirectoryEntry().fullPath === RootDirectory.DOWNLOADS)
558 metrics.recordMediumCount('DownloadsCount', this.fileList_.length);
559 callback();
560 }.bind(this));
561 };
228 562
229 /** 563 /**
230 * Called in case scan has failed. Should send the event. 564 * Called in case scan has failed. Should send the event.
231 * @protected 565 * @protected
232 */ 566 */
233 DirectoryContents.prototype.onError = function() { 567 DirectoryContents.prototype.onScanError = function() {
234 cr.dispatchSimpleEvent(this, 'scan-failed'); 568 this.scanner_ = null;
569 if (this.scanCancelled_)
570 return;
571
572 this.prefetchMetadataQueue_.run(function(callback) {
573 cr.dispatchSimpleEvent(this, 'scan-failed');
574 callback();
575 }.bind(this));
235 }; 576 };
236 577
237 /** 578 /**
238 * Called in case scan has completed succesfully. Should send the event. 579 * Called when some chunk of entries are read by scanner.
580 * @param {Array.<Entry>} entries The list of the scanned entries.
239 * @protected 581 * @protected
240 */ 582 */
241 DirectoryContents.prototype.lastChunkReceived = function() { 583 DirectoryContents.prototype.onNewEntries = function(entries) {
242 this.allChunksFetched_ = true; 584 if (this.scanCancelled_)
243 if (!this.scanCancelled_ && this.pendingMetadataRequests_ === 0) 585 return;
244 cr.dispatchSimpleEvent(this, 'scan-completed'); 586
587 var entriesFiltered = [].filter.call(
588 entries, this.context_.fileFilter.filter.bind(this.context_.fileFilter));
589
590 // Because the prefetchMetadata can be slow, throttling by splitting entries
591 // into smaller chunks to reduce UI latency.
592 // TODO(hidehiko,mtomasz): This should be handled in MetadataCache.
593 var MAX_CHUNK_SIZE = 50;
594 for (var i = 0; i < entriesFiltered.length; i += MAX_CHUNK_SIZE) {
595 var chunk = entriesFiltered.slice(i, i + MAX_CHUNK_SIZE);
596 this.prefetchMetadataQueue_.run(function(chunk, callback) {
597 this.prefetchMetadata(chunk, function() {
598 if (this.scanCancelled_) {
599 // Do nothing if the scanning is cancelled.
600 callback();
601 return;
602 }
603
604 this.fileList_.push.apply(this.fileList_, chunk);
605 cr.dispatchSimpleEvent(this, 'scan-updated');
606 callback();
607 }.bind(this));
608 }.bind(this, chunk));
609 }
245 }; 610 };
246 611
247 /** 612 /**
248 * Cache necessary data before a sort happens. 613 * Cache necessary data before a sort happens.
249 * 614 *
250 * This is called by the table code before a sort happens, so that we can 615 * 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. 616 * go fetch data for the sort field that we may not have yet.
252 * @param {string} field Sort field. 617 * @param {string} field Sort field.
253 * @param {function(Object)} callback Called when done. 618 * @param {function(Object)} callback Called when done.
254 * @private 619 * @private
(...skipping 13 matching lines...) Expand all
268 /** 633 /**
269 * @param {Array.<Entry>} entries Files. 634 * @param {Array.<Entry>} entries Files.
270 * @param {function(Object)} callback Callback on done. 635 * @param {function(Object)} callback Callback on done.
271 */ 636 */
272 DirectoryContents.prototype.reloadMetadata = function(entries, callback) { 637 DirectoryContents.prototype.reloadMetadata = function(entries, callback) {
273 this.context_.metadataCache.clear(entries, '*'); 638 this.context_.metadataCache.clear(entries, '*');
274 this.context_.metadataCache.get(entries, 'filesystem', callback); 639 this.context_.metadataCache.get(entries, 'filesystem', callback);
275 }; 640 };
276 641
277 /** 642 /**
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. 643 * @param {string} name Directory name.
309 * @param {function(DirectoryEntry)} successCallback Called on success. 644 * @param {function(DirectoryEntry)} successCallback Called on success.
310 * @param {function(FileError)} errorCallback On error. 645 * @param {function(FileError)} errorCallback On error.
311 */ 646 */
312 DirectoryContents.prototype.createDirectory = function( 647 DirectoryContents.prototype.createDirectory = function(
313 name, successCallback, errorCallback) { 648 name, successCallback, errorCallback) {
314 throw 'Not implemented.'; 649 throw 'Not implemented.';
315 }; 650 };
316 651
317 652
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 * @return {DirectoryEntry} DirectoryEntry for the currnet entry. 685 * @return {DirectoryEntry} DirectoryEntry for the currnet entry.
351 */ 686 */
352 DirectoryContentsBasic.prototype.getLastNonSearchDirectoryEntry = function() { 687 DirectoryContentsBasic.prototype.getLastNonSearchDirectoryEntry = function() {
353 return this.entry_; 688 return this.entry_;
354 }; 689 };
355 690
356 /** 691 /**
357 * Start directory scan. 692 * Start directory scan.
358 */ 693 */
359 DirectoryContentsBasic.prototype.scan = function() { 694 DirectoryContentsBasic.prototype.scan = function() {
360 if (!this.entry_ || this.entry_ === DirectoryModel.fakeDriveEntry_) { 695 this.scanner_ = new DirectoryContentScanner(this.entry_);
mtomasz 2013/10/01 02:36:39 Do we need to create a new instance of scanner eve
hidehiko 2013/10/01 04:12:23 Good point. In the existing code, scan must be cal
mtomasz 2013/10/01 04:39:55 Can you please add either (1) a TODO line or (2):
hidehiko 2013/10/01 05:28:17 Done.
361 this.lastChunkReceived(); 696 this.scanner_.startScan(this.onNewEntries.bind(this),
362 return; 697 this.onScanCompleted.bind(this),
363 } 698 this.onScanError.bind(this));
364
365 metrics.startInterval('DirectoryScan');
366 this.reader_ = this.entry_.createReader();
367 this.readNextChunk();
368 }; 699 };
369 700
370 /** 701 /**
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. 702 * @param {string} name Directory name.
407 * @param {function(Entry)} successCallback Called on success. 703 * @param {function(Entry)} successCallback Called on success.
408 * @param {function(FileError)} errorCallback On error. 704 * @param {function(FileError)} errorCallback On error.
409 */ 705 */
410 DirectoryContentsBasic.prototype.createDirectory = function( 706 DirectoryContentsBasic.prototype.createDirectory = function(
411 name, successCallback, errorCallback) { 707 name, successCallback, errorCallback) {
708 // TODO(hidehiko): createDirectory should not be the part of
709 // DirectoryContent.
412 if (!this.entry_) { 710 if (!this.entry_) {
413 errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR)); 711 errorCallback(util.createFileError(FileError.INVALID_MODIFICATION_ERR));
414 return; 712 return;
415 } 713 }
416 714
417 var onSuccess = function(newEntry) { 715 var onSuccess = function(newEntry) {
418 this.reloadMetadata([newEntry], function() { 716 this.reloadMetadata([newEntry], function() {
419 successCallback(newEntry); 717 successCallback(newEntry);
420 }); 718 });
421 }; 719 };
422 720
423 this.entry_.getDirectory(name, {create: true, exclusive: true}, 721 this.entry_.getDirectory(name, {create: true, exclusive: true},
424 onSuccess.bind(this), errorCallback); 722 onSuccess.bind(this), errorCallback);
425 }; 723 };
426 724
427 /** 725 /**
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. 726 * @param {FileListContext} context File list context.
447 * @param {DirectoryEntry} dirEntry Current directory. 727 * @param {DirectoryEntry} dirEntry Current directory.
448 * @param {DirectoryEntry} previousDirEntry DirectoryEntry that was current 728 * @param {DirectoryEntry} previousDirEntry DirectoryEntry that was current
449 * before the search. 729 * before the search.
450 * @param {string} query Search query. 730 * @param {string} query Search query.
451 * @constructor 731 * @constructor
452 * @extends {DirectoryContents} 732 * @extends {DirectoryContents}
453 */ 733 */
454 function DirectoryContentsDriveSearch(context, 734 function DirectoryContentsDriveSearch(context,
455 dirEntry, 735 dirEntry,
456 previousDirEntry, 736 previousDirEntry,
457 query) { 737 query) {
458 DirectoryContents.call(this, context); 738 DirectoryContents.call(this, context);
459 this.directoryEntry_ = dirEntry; 739 this.directoryEntry_ = dirEntry;
460 this.previousDirectoryEntry_ = previousDirEntry; 740 this.previousDirectoryEntry_ = previousDirEntry;
461 this.query_ = query; 741 this.query_ = query;
462 this.nextFeed_ = '';
463 this.done_ = false;
464 this.fetchedResultsNum_ = 0;
465 } 742 }
466 743
467 /** 744 /**
468 * Extends DirectoryContents. 745 * Extends DirectoryContents.
469 */ 746 */
470 DirectoryContentsDriveSearch.prototype.__proto__ = DirectoryContents.prototype; 747 DirectoryContentsDriveSearch.prototype.__proto__ = DirectoryContents.prototype;
471 748
472 /** 749 /**
473 * Create the copy of the object, but without scan started. 750 * Create the copy of the object, but without scan started.
474 * @return {DirectoryContentsBasic} Object copy. 751 * @return {DirectoryContentsBasic} Object copy.
(...skipping 25 matching lines...) Expand all
500 */ 777 */
501 DirectoryContentsDriveSearch.prototype.getLastNonSearchDirectoryEntry = 778 DirectoryContentsDriveSearch.prototype.getLastNonSearchDirectoryEntry =
502 function() { 779 function() {
503 return this.previousDirectoryEntry_; 780 return this.previousDirectoryEntry_;
504 }; 781 };
505 782
506 /** 783 /**
507 * Start directory scan. 784 * Start directory scan.
508 */ 785 */
509 DirectoryContentsDriveSearch.prototype.scan = function() { 786 DirectoryContentsDriveSearch.prototype.scan = function() {
510 // Let's give another search a chance to cancel us before we begin. 787 this.scanner_ = new DriveSearchContentScanner(this.query_);
mtomasz 2013/10/01 02:36:39 Same here.
hidehiko 2013/10/01 04:12:23 Ack.
511 setTimeout(this.readNextChunk.bind(this), 788 this.scanner_.startScan(this.onNewEntries.bind(this),
512 DirectoryContentsDriveSearch.SCAN_DELAY); 789 this.onScanCompleted.bind(this),
790 this.onScanError.bind(this));
513 }; 791 };
514 792
515 /** 793 /**
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. 794 * @param {FileListContext} context File list context.
559 * @param {DirectoryEntry} dirEntry Current directory. 795 * @param {DirectoryEntry} dirEntry Current directory.
560 * @param {string} query Search query. 796 * @param {string} query Search query.
561 * @constructor 797 * @constructor
562 * @extends {DirectoryContents} 798 * @extends {DirectoryContents}
563 */ 799 */
564 function DirectoryContentsLocalSearch(context, dirEntry, query) { 800 function DirectoryContentsLocalSearch(context, dirEntry, query) {
565 DirectoryContents.call(this, context); 801 DirectoryContents.call(this, context);
566 this.directoryEntry_ = dirEntry; 802 this.directoryEntry_ = dirEntry;
567 this.query_ = query.toLowerCase(); 803 this.query_ = query;
568 } 804 }
569 805
570 /** 806 /**
571 * Extends DirectoryContents 807 * Extends DirectoryContents
572 */ 808 */
573 DirectoryContentsLocalSearch.prototype.__proto__ = DirectoryContents.prototype; 809 DirectoryContentsLocalSearch.prototype.__proto__ = DirectoryContents.prototype;
574 810
575 /** 811 /**
576 * Create the copy of the object, but without scan started. 812 * Create the copy of the object, but without scan started.
577 * @return {DirectoryContentsBasic} Object copy. 813 * @return {DirectoryContentsBasic} Object copy.
(...skipping 25 matching lines...) Expand all
603 DirectoryContentsLocalSearch.prototype.getLastNonSearchDirectoryEntry = 839 DirectoryContentsLocalSearch.prototype.getLastNonSearchDirectoryEntry =
604 function() { 840 function() {
605 return this.directoryEntry_; 841 return this.directoryEntry_;
606 }; 842 };
607 843
608 /** 844 /**
609 * Start directory scan/search operation. Either 'scan-completed' or 845 * Start directory scan/search operation. Either 'scan-completed' or
610 * 'scan-failed' event will be fired upon completion. 846 * 'scan-failed' event will be fired upon completion.
611 */ 847 */
612 DirectoryContentsLocalSearch.prototype.scan = function() { 848 DirectoryContentsLocalSearch.prototype.scan = function() {
613 this.pendingScans_ = 0; 849 this.scanner_ =
614 this.scanDirectory_(this.directoryEntry_); 850 new LocalSearchContentScanner(this.directoryEntry_, this.query_);
851 this.scanner_.startScan(this.onNewEntries.bind(this),
852 this.onScanCompleted.bind(this),
853 this.onScanError.bind(this));
615 }; 854 };
616 855
617 /** 856 /**
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(). 857 * DirectoryContents to list Drive files using searchDriveMetadata().
683 * 858 *
684 * @param {FileListContext} context File list context. 859 * @param {FileListContext} context File list context.
685 * @param {DirectoryEntry} driveDirEntry Directory for actual Drive. 860 * @param {DirectoryEntry} driveDirEntry Directory for actual Drive.
686 * @param {DirectoryEntry} fakeDirEntry Fake directory representing the set of 861 * @param {DirectoryEntry} fakeDirEntry Fake directory representing the set of
687 * result files. This serves as a top directory for this search. 862 * result files. This serves as a top directory for this search.
688 * @param {string} query Search query to filter the files. 863 * @param {string} query Search query to filter the files.
689 * @param {DirectoryContentsDriveSearchMetadata.SearchType} searchType 864 * @param {DriveMetadataSearchContentScanner.SearchType} searchType
690 * Type of search. searchDriveMetadata will restricts the entries based on 865 * Type of search. searchDriveMetadata will restricts the entries based on
691 * the given search type. 866 * the given search type.
692 * @constructor 867 * @constructor
693 * @extends {DirectoryContents} 868 * @extends {DirectoryContents}
694 */ 869 */
695 function DirectoryContentsDriveSearchMetadata(context, 870 function DirectoryContentsDriveSearchMetadata(context,
696 driveDirEntry, 871 driveDirEntry,
697 fakeDirEntry, 872 fakeDirEntry,
698 query, 873 query,
699 searchType) { 874 searchType) {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
742 DirectoryContentsDriveSearchMetadata.prototype.getLastNonSearchDirectoryEntry = 917 DirectoryContentsDriveSearchMetadata.prototype.getLastNonSearchDirectoryEntry =
743 function() { 918 function() {
744 return this.driveDirEntry_; 919 return this.driveDirEntry_;
745 }; 920 };
746 921
747 /** 922 /**
748 * Start directory scan/search operation. Either 'scan-completed' or 923 * Start directory scan/search operation. Either 'scan-completed' or
749 * 'scan-failed' event will be fired upon completion. 924 * 'scan-failed' event will be fired upon completion.
750 */ 925 */
751 DirectoryContentsDriveSearchMetadata.prototype.scan = function() { 926 DirectoryContentsDriveSearchMetadata.prototype.scan = function() {
752 this.readNextChunk(); 927 this.scanner_ =
928 new DriveMetadataSearchContentScanner(this.query_, this.searchType_);
929 this.scanner_.startScan(this.onNewEntries.bind(this),
930 this.onScanCompleted.bind(this),
931 this.onScanError.bind(this));
753 }; 932 };
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

Powered by Google App Engine
This is Rietveld 408576698