OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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(hidehiko,mtomasz): this scan method must be called at most once. |
361 this.lastChunkReceived(); | 708 // Remove such a 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 Loading... |
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 Loading... |
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 Loading... |
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 }; | |
OLD | NEW |