OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 // Setting the src of an img to an empty string can crash the browser, so we | 5 // Setting the src of an img to an empty string can crash the browser, so we |
6 // use an empty 1x1 gif instead. | 6 // use an empty 1x1 gif instead. |
7 const EMPTY_IMAGE_URI = 'data:image/gif;base64,' | 7 const EMPTY_IMAGE_URI = 'data:image/gif;base64,' |
8 + 'R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D'; | 8 + 'R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D'; |
9 | 9 |
10 var g_slideshow_data = null; | 10 var g_slideshow_data = null; |
11 | 11 |
12 const IMAGE_EDITOR_ENABLED = false; | 12 const IMAGE_EDITOR_ENABLED = false; |
13 | 13 |
14 // If directory files changes too often, don't rescan directory more than once | |
15 // per specified interva; | |
rginda
2011/08/31 00:32:58
typo: s/;/l./
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
16 const SIMULTANEOUS_RESCAN_INTERVAL = 1000; | |
17 | |
14 /** | 18 /** |
15 * FileManager constructor. | 19 * FileManager constructor. |
16 * | 20 * |
17 * FileManager objects encapsulate the functionality of the file selector | 21 * FileManager objects encapsulate the functionality of the file selector |
18 * dialogs, as well as the full screen file manager application (though the | 22 * dialogs, as well as the full screen file manager application (though the |
19 * latter is not yet implemented). | 23 * latter is not yet implemented). |
20 * | 24 * |
21 * @param {HTMLElement} dialogDom The DOM node containing the prototypical | 25 * @param {HTMLElement} dialogDom The DOM node containing the prototypical |
22 * dialog UI. | 26 * dialog UI. |
23 * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing | 27 * @param {DOMFileSystem} filesystem The HTML5 filesystem object representing |
(...skipping 21 matching lines...) Expand all Loading... | |
45 | 49 |
46 this.selection = null; | 50 this.selection = null; |
47 | 51 |
48 this.clipboard_ = null; // Current clipboard, or null if empty. | 52 this.clipboard_ = null; // Current clipboard, or null if empty. |
49 | 53 |
50 this.butterTimer_ = null; | 54 this.butterTimer_ = null; |
51 this.currentButter_ = null; | 55 this.currentButter_ = null; |
52 | 56 |
53 // True if we should filter out files that start with a dot. | 57 // True if we should filter out files that start with a dot. |
54 this.filterFiles_ = true; | 58 this.filterFiles_ = true; |
59 this.subscribedOnDirectoryChanges_ = false; | |
60 this.rescanPending_ = []; | |
rginda
2011/08/31 00:32:58
rescanPending_ and rescanRunning_ are *very* simil
Dmitry Zvorygin
2011/09/12 11:12:23
We need both array and flag because we empty array
| |
61 this.rescanRunning_ = false; | |
55 | 62 |
56 this.commands_ = {}; | 63 this.commands_ = {}; |
57 | 64 |
58 this.document_ = dialogDom.ownerDocument; | 65 this.document_ = dialogDom.ownerDocument; |
59 this.dialogType_ = this.params_.type || FileManager.DialogType.FULL_PAGE; | 66 this.dialogType_ = this.params_.type || FileManager.DialogType.FULL_PAGE; |
60 | 67 |
61 this.alert = new cr.ui.dialogs.AlertDialog(this.dialogDom_); | 68 this.alert = new cr.ui.dialogs.AlertDialog(this.dialogDom_); |
62 this.confirm = new cr.ui.dialogs.ConfirmDialog(this.dialogDom_); | 69 this.confirm = new cr.ui.dialogs.ConfirmDialog(this.dialogDom_); |
63 this.prompt = new cr.ui.dialogs.PromptDialog(this.dialogDom_); | 70 this.prompt = new cr.ui.dialogs.PromptDialog(this.dialogDom_); |
64 | 71 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
101 this.dialogType_ == FileManager.DialogType.SELECT_OPEN_MULTI_FILE); | 108 this.dialogType_ == FileManager.DialogType.SELECT_OPEN_MULTI_FILE); |
102 | 109 |
103 // DirectoryEntry representing the current directory of the dialog. | 110 // DirectoryEntry representing the current directory of the dialog. |
104 this.currentDirEntry_ = null; | 111 this.currentDirEntry_ = null; |
105 | 112 |
106 this.copyManager_ = new FileCopyManager(); | 113 this.copyManager_ = new FileCopyManager(); |
107 this.copyManager_.addEventListener('copy-progress', | 114 this.copyManager_.addEventListener('copy-progress', |
108 this.onCopyProgress_.bind(this)); | 115 this.onCopyProgress_.bind(this)); |
109 | 116 |
110 window.addEventListener('popstate', this.onPopState_.bind(this)); | 117 window.addEventListener('popstate', this.onPopState_.bind(this)); |
118 window.addEventListener('unload', this.onUnload_.bind(this)); | |
119 | |
111 this.addEventListener('directory-changed', | 120 this.addEventListener('directory-changed', |
112 this.onDirectoryChanged_.bind(this)); | 121 this.onDirectoryChanged_.bind(this)); |
113 this.addEventListener('selection-summarized', | 122 this.addEventListener('selection-summarized', |
114 this.onSelectionSummarized_.bind(this)); | 123 this.onSelectionSummarized_.bind(this)); |
115 | 124 |
116 // The list of archives requested to mount. We will show contents once | 125 // The list of archives requested to mount. We will show contents once |
117 // archive is mounted, but only for mounts from within this filebrowser tab. | 126 // archive is mounted, but only for mounts from within this filebrowser tab. |
118 this.mountRequests_ = []; | 127 this.mountRequests_ = []; |
119 chrome.fileBrowserPrivate.onMountCompleted.addListener( | 128 chrome.fileBrowserPrivate.onMountCompleted.addListener( |
120 this.onMountCompleted_.bind(this)); | 129 this.onMountCompleted_.bind(this)); |
121 | 130 |
131 chrome.fileBrowserPrivate.onFileChanged.addListener( | |
132 this.onFileChanged_.bind(this)); | |
133 | |
122 var self = this; | 134 var self = this; |
123 | 135 |
124 // The list of active mount points to distinct them from other directories. | 136 // The list of active mount points to distinct them from other directories. |
125 chrome.fileBrowserPrivate.getMountPoints(function(mountPoints) { | 137 chrome.fileBrowserPrivate.getMountPoints(function(mountPoints) { |
126 self.mountPoints_ = mountPoints; | 138 self.mountPoints_ = mountPoints; |
127 }); | 139 }); |
128 | 140 |
129 chrome.fileBrowserHandler.onExecute.addListener( | 141 chrome.fileBrowserHandler.onExecute.addListener( |
130 this.onFileTaskExecute_.bind(this)); | 142 this.onFileTaskExecute_.bind(this)); |
131 | 143 |
(...skipping 2206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2338 | 2350 |
2339 this.updateCommands_(); | 2351 this.updateCommands_(); |
2340 this.updateOkButton_(); | 2352 this.updateOkButton_(); |
2341 | 2353 |
2342 // New folder should never be enabled in the root or media/ directories. | 2354 // New folder should never be enabled in the root or media/ directories. |
2343 this.newFolderButton_.disabled = isSystemDirEntry(this.currentDirEntry_); | 2355 this.newFolderButton_.disabled = isSystemDirEntry(this.currentDirEntry_); |
2344 | 2356 |
2345 this.document_.title = this.currentDirEntry_.fullPath; | 2357 this.document_.title = this.currentDirEntry_.fullPath; |
2346 | 2358 |
2347 var self = this; | 2359 var self = this; |
2360 | |
2361 if (this.subscribedOnDirectoryChanges_) { | |
2362 chrome.fileBrowserPrivate.removeFileWatch(event.previousDirEntry.toURL(), | |
2363 function(result) { | |
2364 if (!result) { | |
2365 console.log('Failed to remove file watch'); | |
2366 } | |
2367 }); | |
2368 } | |
2369 | |
2370 this.subscribedOnDirectoryChanges_ = true; | |
2371 chrome.fileBrowserPrivate.addFileWatch(event.newDirEntry.toURL(), | |
2372 function(result) { | |
2373 if (!result) { | |
2374 console.log('Failed to add file watch'); | |
2375 } | |
2376 }); | |
2377 | |
2348 this.rescanDirectory_(function() { | 2378 this.rescanDirectory_(function() { |
2349 if (event.selectedEntry) | 2379 if (event.selectedEntry) |
2350 self.selectEntry(event.selectedEntry); | 2380 self.selectEntry(event.selectedEntry); |
2351 // For tests that open the dialog to empty directories, everything | 2381 // For tests that open the dialog to empty directories, everything |
2352 // is loaded at this point. | 2382 // is loaded at this point. |
2353 chrome.test.sendMessage('directory-change-complete'); | 2383 chrome.test.sendMessage('directory-change-complete'); |
2354 }); | 2384 }); |
2355 }; | 2385 }; |
2356 | 2386 |
2357 /** | 2387 /** |
2358 * Update the UI when a disk is mounted or unmounted. | 2388 * Update the UI when a disk is mounted or unmounted. |
2359 * | 2389 * |
2360 * @param {string} path The path that has been mounted or unmounted. | 2390 * @param {string} path The path that has been mounted or unmounted. |
2361 */ | 2391 */ |
2362 FileManager.prototype.onDiskChanged_ = function(event) { | 2392 FileManager.prototype.onDiskChanged_ = function(event) { |
2363 if (event.eventType == 'added') { | 2393 if (event.eventType == 'added') { |
2364 this.changeDirectory(event.volumeInfo.mountPath); | 2394 this.changeDirectory(event.volumeInfo.mountPath); |
2365 } else if (event.eventType == 'removed') { | 2395 } else if (event.eventType == 'removed') { |
2366 if (this.currentDirEntry_ && | 2396 if (this.currentDirEntry_ && |
2367 isParentPath(event.volumeInfo.mountPath, | 2397 isParentPath(event.volumeInfo.mountPath, |
2368 this.currentDirEntry_.fullPath)) { | 2398 this.currentDirEntry_.fullPath)) { |
2369 this.changeDirectory(getParentPath(event.volumeInfo.mountPath)); | 2399 this.changeDirectory(getParentPath(event.volumeInfo.mountPath)); |
2370 } | 2400 } |
2371 } | 2401 } |
2372 }; | 2402 }; |
2373 | 2403 |
2374 /** | 2404 /** |
2375 * Rescan the current directory, refreshing the list. | 2405 * Rescan the current directory, refreshing the list. |
2376 * | 2406 * |
rginda
2011/08/31 00:32:58
Please add some description of the new queuing nat
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
2377 * @param {function()} opt_callback Optional function to invoke when the | 2407 * @param {function()} opt_callback Optional function to invoke when the |
2378 * rescan is complete. | 2408 * rescan is complete. |
2409 * | |
2410 * @param {function()} opt_onError Optional function to invoke when the | |
2411 * rescan fails. | |
2379 */ | 2412 */ |
2380 FileManager.prototype.rescanDirectory_ = function(opt_callback) { | 2413 FileManager.prototype.rescanDirectory_ = function(opt_callback, opt_onError) { |
2381 var self = this; | |
2382 var reader; | |
2383 | |
2384 function onReadSome(entries) { | |
2385 if (entries.length == 0) { | |
2386 if (opt_callback) | |
2387 opt_callback(); | |
2388 return; | |
2389 } | |
2390 | |
2391 // Splice takes the to-be-spliced-in array as individual parameters, | |
2392 // rather than as an array, so we need to perform some acrobatics... | |
2393 var spliceArgs = [].slice.call(entries); | |
2394 | |
2395 // Hide files that start with a dot ('.'). | |
2396 // TODO(rginda): User should be able to override this. Support for other | |
2397 // commonly hidden patterns might be nice too. | |
2398 if (self.filterFiles_) { | |
2399 spliceArgs = spliceArgs.filter(function(e) { | |
2400 return e.name.substr(0, 1) != '.'; | |
2401 }); | |
2402 } | |
2403 | |
2404 spliceArgs.unshift(0, 0); // index, deleteCount | |
2405 self.dataModel_.splice.apply(self.dataModel_, spliceArgs); | |
2406 | |
2407 // Keep reading until entries.length is 0. | |
2408 reader.readEntries(onReadSome); | |
2409 }; | |
2410 | |
2411 // Updated when a user clicks on the label of a file, used to detect | 2414 // Updated when a user clicks on the label of a file, used to detect |
2412 // when a click is eligible to trigger a rename. Can be null, or | 2415 // when a click is eligible to trigger a rename. Can be null, or |
2413 // an object with 'path' and 'date' properties. | 2416 // an object with 'path' and 'date' properties. |
2414 this.lastLabelClick_ = null; | 2417 this.lastLabelClick_ = null; |
2415 | 2418 |
2416 // Clear the table first. | 2419 // Clear the table first. |
2417 this.dataModel_.splice(0, this.dataModel_.length); | 2420 this.dataModel_.splice(0, this.dataModel_.length); |
2418 this.currentList_.selectionModel.clear(); | 2421 this.currentList_.selectionModel.clear(); |
2419 | 2422 |
2420 this.updateBreadcrumbs_(); | 2423 this.updateBreadcrumbs_(); |
2421 | 2424 |
2422 if (this.currentDirEntry_.fullPath != '/') { | 2425 if (this.currentDirEntry_.fullPath != '/') { |
2426 // Add current request to pending result list | |
rginda
2011/08/31 00:32:58
rescanDirectory_ is getting pretty large, can this
Dmitry Zvorygin
2011/09/12 11:12:23
I think it's little useless because that function
| |
2427 this.rescanPending_.push({onSuccess:opt_callback, onError:opt_onError}); | |
2428 | |
2429 if (this.rescanRunning_) { | |
rginda
2011/08/31 00:32:58
No braces necessary here.
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
2430 return; | |
2431 } | |
2432 | |
2433 this.rescanRunning_ = true; | |
2434 | |
2435 // Save list of items to notify locally in case we'll receive new requests | |
rginda
2011/08/31 00:32:58
I don't think this comment is enough to describe w
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
2436 // to refresh while refreshing | |
2437 var callbacks = this.rescanPending_; | |
2438 | |
2439 this.rescanPending_ = []; | |
2440 | |
2441 var self = this; | |
2442 var reader; | |
2443 | |
2444 function onError() { | |
2445 | |
2446 for (var i= 0; i < callbacks.length; i++) { | |
2447 if (callbacks[i].onError) { | |
rginda
2011/08/31 00:32:58
No braces.
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
2448 callbacks[i].onError(); | |
rginda
2011/08/31 00:32:58
If any of these callbacks throw, rescanRunning wil
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
2449 } | |
2450 } | |
2451 | |
2452 if (self.rescanPending_.length > 0) { | |
2453 setTimeout(self.rescanDirectory_.bind(self), | |
2454 SIMULTANEOUS_RESCAN_INTERVAL); | |
2455 } | |
2456 | |
2457 self.rescanRunning_ = false; | |
2458 } | |
2459 | |
2460 function onReadSome(entries) { | |
2461 if (entries.length == 0) { | |
2462 | |
2463 for (var i= 0; i < callbacks.length; i++) { | |
2464 if (callbacks[i].onSuccess) { | |
2465 callbacks[i].onSuccess(); | |
rginda
2011/08/31 00:32:58
Same issue with failed callbacks here. Also, unne
Dmitry Zvorygin
2011/09/12 11:12:23
Done.
| |
2466 } | |
2467 } | |
2468 | |
2469 if (self.rescanPending_.length > 0) { | |
2470 setTimeout(self.rescanDirectory_.bind(self), | |
2471 SIMULTANEOUS_RESCAN_INTERVAL); | |
2472 } | |
2473 | |
2474 self.rescanRunning_ = false; | |
2475 return; | |
2476 } | |
2477 | |
2478 // Splice takes the to-be-spliced-in array as individual parameters, | |
2479 // rather than as an array, so we need to perform some acrobatics... | |
2480 var spliceArgs = [].slice.call(entries); | |
2481 | |
2482 // Hide files that start with a dot ('.'). | |
2483 // TODO(rginda): User should be able to override this. Support for other | |
2484 // commonly hidden patterns might be nice too. | |
2485 if (self.filterFiles_) { | |
2486 spliceArgs = spliceArgs.filter(function(e) { | |
2487 return e.name.substr(0, 1) != '.'; | |
2488 }); | |
2489 } | |
2490 | |
2491 spliceArgs.unshift(0, 0); // index, deleteCount | |
2492 self.dataModel_.splice.apply(self.dataModel_, spliceArgs); | |
2493 | |
2494 // Keep reading until entries.length is 0. | |
2495 reader.readEntries(onReadSome, onError); | |
2496 }; | |
2497 | |
2423 // If not the root directory, just read the contents. | 2498 // If not the root directory, just read the contents. |
2424 reader = this.currentDirEntry_.createReader(); | 2499 reader = this.currentDirEntry_.createReader(); |
2425 reader.readEntries(onReadSome); | 2500 reader.readEntries(onReadSome, onError); |
2426 return; | 2501 return; |
2427 } | 2502 } |
2428 | 2503 |
2429 // Otherwise, use the provided list of root subdirectories, since the | 2504 // Otherwise, use the provided list of root subdirectories, since the |
2430 // real local filesystem root directory (the one we use outside the | 2505 // real local filesystem root directory (the one we use outside the |
2431 // harness) can't be enumerated yet. | 2506 // harness) can't be enumerated yet. |
2432 var spliceArgs = [].slice.call(this.rootEntries_); | 2507 var spliceArgs = [].slice.call(this.rootEntries_); |
2433 spliceArgs.unshift(0, 0); // index, deleteCount | 2508 spliceArgs.unshift(0, 0); // index, deleteCount |
2434 self.dataModel_.splice.apply(self.dataModel_, spliceArgs); | 2509 self.dataModel_.splice.apply(self.dataModel_, spliceArgs); |
2435 | 2510 |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2476 if (event.button != 1) | 2551 if (event.button != 1) |
2477 return; | 2552 return; |
2478 | 2553 |
2479 var li = this.findListItem_(event); | 2554 var li = this.findListItem_(event); |
2480 if (!li) { | 2555 if (!li) { |
2481 console.log('li not found', event); | 2556 console.log('li not found', event); |
2482 return; | 2557 return; |
2483 } | 2558 } |
2484 }; | 2559 }; |
2485 | 2560 |
2561 FileManager.prototype.onUnload_ = function(event) { | |
2562 if (this.subscribedOnDirectoryChanges_) { | |
2563 chrome.fileBrowserPrivate.removeFileWatch(event.previousDirEntry.toURL(), | |
2564 function(result) { | |
2565 if (!result) { | |
2566 console.log('Failed to remove file watch'); | |
2567 } | |
2568 }); | |
2569 } | |
2570 }; | |
2571 | |
2572 FileManager.prototype.onFileChanged_ = function(event) { | |
2573 // It might be massive change, so let's note somehow, that we need | |
rginda
2011/08/31 00:32:58
How about just moving this code into a 'rescanDire
| |
2574 // rescanning and then wait some time | |
2575 | |
2576 if (this.rescanPending_.length == 0) { | |
2577 this.rescanPending_.push({onSuccess:undefined, onError:undefined}); | |
2578 | |
2579 // If rescan isn't going to run without | |
2580 // our interruption, then say that we need to run it | |
2581 if (!this.rescanRunning_) { | |
2582 setTimeout(this.rescanDirectory_.bind(this), | |
2583 SIMULTANEOUS_RESCAN_INTERVAL); | |
2584 } | |
2585 } | |
2586 }; | |
2587 | |
2486 /** | 2588 /** |
2487 * Determine whether or not a click should initiate a rename. | 2589 * Determine whether or not a click should initiate a rename. |
2488 * | 2590 * |
2489 * Renames can happen on mouse click if the user clicks on a label twice, | 2591 * Renames can happen on mouse click if the user clicks on a label twice, |
2490 * at least a half second apart. | 2592 * at least a half second apart. |
2491 */ | 2593 */ |
2492 FileManager.prototype.allowRenameClick_ = function(event, row) { | 2594 FileManager.prototype.allowRenameClick_ = function(event, row) { |
2493 if (this.dialogType_ != FileManager.DialogType.FULL_PAGE || | 2595 if (this.dialogType_ != FileManager.DialogType.FULL_PAGE || |
2494 this.currentDirEntry_.name == '' || | 2596 this.currentDirEntry_.name == '' || |
2495 isSystemDirEntry(this.currentDirEntry_)) { | 2597 isSystemDirEntry(this.currentDirEntry_)) { |
(...skipping 499 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2995 | 3097 |
2996 if (msg) { | 3098 if (msg) { |
2997 console.log('no no no'); | 3099 console.log('no no no'); |
2998 this.alert.show(msg, onAccept); | 3100 this.alert.show(msg, onAccept); |
2999 return false; | 3101 return false; |
3000 } | 3102 } |
3001 | 3103 |
3002 return true; | 3104 return true; |
3003 }; | 3105 }; |
3004 })(); | 3106 })(); |
OLD | NEW |