Commit f8561ba1 authored by Austin Tankiang's avatar Austin Tankiang Committed by Commit Bot

Add new pin visual signal to indicate Drive pinning progress

A new onPinTransfersUpdated event is also added to FileManagerPrivate
API, which will be fired by Chrome to notify the UI of pinning status.
The new visual signal is hooked up to and updated by this event.

Bug: 1137617
Change-Id: Icc5cb9f0b2851acc1d10839c9532d6d9a7e14fb3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2474076Reviewed-by: default avatarJun Mukai <mukai@chromium.org>
Reviewed-by: default avatarSergei Datsenko <dats@chromium.org>
Reviewed-by: default avatarLuciano Pacheco <lucmult@chromium.org>
Commit-Queue: Austin Tankiang <austinct@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821576}
parent 5ae6c42e
......@@ -737,6 +737,10 @@ std::unique_ptr<base::DictionaryValue> GetFileManagerStrings() {
SET_STRING("OFFLINE_MESSAGE", IDS_FILE_BROWSER_OFFLINE_MESSAGE);
SET_STRING("OFFLINE_MESSAGE_PLURAL", IDS_FILE_BROWSER_OFFLINE_MESSAGE_PLURAL);
SET_STRING("OFFLINE_BANNER_MESSAGE", IDS_FILE_BROWSER_OFFLINE_BANNER_MESSAGE);
SET_STRING("OFFLINE_PROGRESS_MESSAGE",
IDS_FILE_BROWSER_OFFLINE_PROGRESS_MESSAGE);
SET_STRING("OFFLINE_PROGRESS_MESSAGE_PLURAL",
IDS_FILE_BROWSER_OFFLINE_PROGRESS_MESSAGE_PLURAL);
SET_STRING("OK_LABEL", IDS_FILE_BROWSER_OK_LABEL);
SET_STRING("ONE_DIRECTORY_SELECTED", IDS_FILE_BROWSER_ONE_DIRECTORY_SELECTED);
SET_STRING("ONE_FILE_SELECTED", IDS_FILE_BROWSER_ONE_FILE_SELECTED);
......
......@@ -1394,6 +1394,8 @@ interface Events {
static void onFileTransfersUpdated(FileTransferStatus event);
static void onPinTransfersUpdated(FileTransferStatus event);
static void onCopyProgress(long copyId, CopyProgressStatus status);
static void onDirectoryChanged(FileWatchEvent event);
......
......@@ -484,6 +484,7 @@ enum HistogramValue {
WALLPAPER_PRIVATE_ON_CLOSE_PREVIEW_WALLPAPER = 462,
PASSWORDS_PRIVATE_ON_WEAK_CREDENTIALS_CHANGED = 463,
ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED = 464,
FILE_MANAGER_PRIVATE_ON_PIN_TRANSFERS_UPDATED = 465,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
......
......@@ -1203,6 +1203,9 @@ chrome.fileManagerPrivate.onMountCompleted;
/** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onFileTransfersUpdated;
/** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onPinTransfersUpdated;
/** @type {!ChromeEvent} */
chrome.fileManagerPrivate.onCopyProgress;
......
......@@ -23575,6 +23575,7 @@ Called by update_extension_histograms.py.-->
<int value="462" label="WALLPAPER_PRIVATE_ON_CLOSE_PREVIEW_WALLPAPER"/>
<int value="463" label="PASSWORDS_PRIVATE_ON_WEAK_CREDENTIALS_CHANGED"/>
<int value="464" label="ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED"/>
<int value="465" label="FILE_MANAGER_PRIVATE_ON_PIN_TRANSFERS_UPDATED"/>
</enum>
<enum name="ExtensionFileWriteResult">
......@@ -1188,6 +1188,12 @@
<message name="IDS_FILE_BROWSER_OFFLINE_BANNER_MESSAGE" desc="Message informing the user that they can make files available offline.">
You can make files available offline for access when you don't have internet connection.
</message>
<message name="IDS_FILE_BROWSER_OFFLINE_PROGRESS_MESSAGE" desc="Message informing the user a Google Drive file is being downloaded for offline use.">
Making <ph name="FILE_NAME">$1<ex>document.pdf</ex></ph> available offline
</message>
<message name="IDS_FILE_BROWSER_OFFLINE_PROGRESS_MESSAGE_PLURAL" desc="Message informing the user multiple Google Drive files are being downloaded for offline use.">
Making <ph name="NUMBER_OF_ITEMS">$1<ex>3</ex></ph> files available offline
</message>
<message name="IDS_FILE_BROWSER_QUICK_VIEW_NO_PLAYBACK_AVAILABLE" desc="Massage for user to notify no playback is available.">
No playback available
</message>
......
4b94d196f25d0a38dea824437eda991d8d15c488
\ No newline at end of file
8eabbae12b81bf8813208bd4ff8b189159ae697c
\ No newline at end of file
......@@ -43,13 +43,26 @@ class DriveSyncHandlerImpl extends cr.EventTarget {
this.errorIdCounter_ = this.driveErrorIdMax_ + 1;
/**
* Progress center item.
* Progress center item for sync status.
* @type {ProgressCenterItem}
* @const
* @private
*/
this.item_ = new ProgressCenterItem();
this.item_.id = 'drive-sync';
this.syncItem_ = new ProgressCenterItem();
this.syncItem_.id = 'drive-sync';
// Set to canceled so that it starts out hidden when sent to ProgressCenter.
this.syncItem_.state = ProgressItemState.CANCELED;
/**
* Progress center item for pinning status.
* @type {ProgressCenterItem}
* @const
* @private
*/
this.pinItem_ = new ProgressCenterItem();
this.pinItem_.id = 'drive-pin';
// Set to canceled so that it starts out hidden when sent to ProgressCenter.
this.pinItem_.state = ProgressItemState.CANCELED;
/**
* If the property is true, this item is syncing.
......@@ -80,12 +93,32 @@ class DriveSyncHandlerImpl extends cr.EventTarget {
this.SPEED_BUFFER_WINDOW_ = 30;
/**
* Speedometer track speed and remaining time of sync.
* @const {fileOperationUtil.Speedometer}
* Speedometers to track speed and remaining time of sync.
* @const {Object<string, fileOperationUtil.Speedometer>}
* @private
*/
this.speedometer_ =
new fileOperationUtil.Speedometer(this.SPEED_BUFFER_WINDOW_);
this.speedometers_ = {
[this.syncItem_.id]:
new fileOperationUtil.Speedometer(this.SPEED_BUFFER_WINDOW_),
[this.pinItem_.id]:
new fileOperationUtil.Speedometer(this.SPEED_BUFFER_WINDOW_),
};
Object.freeze(this.speedometers_);
/**
* Drive sync messages for each id.
* @const {Object<string, {single: string, plural: string}>}
* @private
*/
this.statusMessages_ = {
[this.syncItem_.id]:
{single: 'SYNC_FILE_NAME', plural: 'SYNC_FILE_NUMBER'},
[this.pinItem_.id]: {
single: 'OFFLINE_PROGRESS_MESSAGE',
plural: 'OFFLINE_PROGRESS_MESSAGE_PLURAL'
},
};
Object.freeze(this.statusMessages_);
/**
* Rate limiter which is used to avoid sending update request for progress
......@@ -93,13 +126,16 @@ class DriveSyncHandlerImpl extends cr.EventTarget {
* @private {AsyncUtil.RateLimiter}
*/
this.progressRateLimiter_ = new AsyncUtil.RateLimiter(() => {
this.progressCenter_.updateItem(this.item_);
this.progressCenter_.updateItem(this.syncItem_);
this.progressCenter_.updateItem(this.pinItem_);
}, 2000);
// Register events.
chrome.fileManagerPrivate.onFileTransfersUpdated.addListener(
this.onFileTransfersUpdated_.bind(this));
this.onFileTransfersStatusReceived_.bind(this, this.syncItem_));
chrome.fileManagerPrivate.onPinTransfersUpdated.addListener(
this.onFileTransfersStatusReceived_.bind(this, this.pinItem_));
chrome.fileManagerPrivate.onDriveSyncError.addListener(
this.onDriveSyncError_.bind(this));
chrome.notifications.onButtonClicked.addListener(
......@@ -153,21 +189,23 @@ class DriveSyncHandlerImpl extends cr.EventTarget {
}
/**
* Handles file transfer updated events.
* Handles file transfer status updates and updates the given item
* accordingly.
* @param {ProgressCenterItem} item Item to update.
* @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer
* status.
* @private
*/
onFileTransfersUpdated_(status) {
async onFileTransfersStatusReceived_(item, status) {
switch (status.transferState) {
case 'in_progress':
this.updateItem_(status);
await this.updateItem_(item, status);
break;
case 'completed':
case 'failed':
if ((status.hideWhenZeroJobs && status.num_total_jobs === 0) ||
(!status.hideWhenZeroJobs && status.num_total_jobs === 1)) {
this.removeItem_(status);
await this.removeItem_(item, status);
}
break;
default:
......@@ -177,60 +215,66 @@ class DriveSyncHandlerImpl extends cr.EventTarget {
}
/**
* Updates the item involved with the given status.
* Updates the given progress status item using a transfer status update.
* @param {ProgressCenterItem} item Item to update.
* @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer
* status.
* @private
*/
updateItem_(status) {
this.queue_.run(callback => {
window.webkitResolveLocalFileSystemURL(
status.fileUrl,
entry => {
this.item_.state = ProgressItemState.PROGRESSING;
this.item_.type = ProgressItemType.SYNC;
this.item_.quiet = true;
this.syncing_ = true;
if (status.num_total_jobs > 1) {
this.item_.message =
strf('SYNC_FILE_NUMBER', status.num_total_jobs);
} else {
this.item_.message = strf('SYNC_FILE_NAME', entry.name);
}
this.item_.progressValue = status.processed || 0;
this.item_.progressMax = status.total || 0;
this.speedometer_.setTotalBytes(this.item_.progressMax);
this.speedometer_.update(this.item_.progressValue);
this.item_.currentSpeed = this.speedometer_.getCurrentSpeed();
this.item_.averageSpeed = this.speedometer_.getAverageSpeed();
this.item_.remainingTime = this.speedometer_.getRemainingTime();
this.progressRateLimiter_.run();
callback();
},
error => {
console.warn(
'Resolving URL ' + status.fileUrl + ' is failed: ', error);
callback();
});
});
async updateItem_(item, status) {
const unlock = await this.queue_.lock();
try {
const entry = await new Promise((resolve, reject) => {
window.webkitResolveLocalFileSystemURL(status.fileUrl, resolve, reject);
});
item.state = ProgressItemState.PROGRESSING;
item.type = ProgressItemType.SYNC;
item.quiet = true;
this.syncing_ = true;
if (status.num_total_jobs > 1) {
item.message =
strf(this.statusMessages_[item.id].plural, status.num_total_jobs);
} else {
item.message = strf(this.statusMessages_[item.id].single, entry.name);
}
item.progressValue = status.processed || 0;
item.progressMax = status.total || 0;
const speedometer = this.speedometers_[item.id];
speedometer.setTotalBytes(item.progressMax);
speedometer.update(item.progressValue);
item.currentSpeed = speedometer.getCurrentSpeed();
item.averageSpeed = speedometer.getAverageSpeed();
item.remainingTime = speedometer.getRemainingTime();
this.progressRateLimiter_.run();
} catch (error) {
console.warn('Resolving URL ' + status.fileUrl + ' is failed: ', error);
} finally {
unlock();
}
}
/**
* Removes the item involved with the given status.
* Removes an item due to the given transfer status update.
* @param {ProgressCenterItem} item Item to remove.
* @param {chrome.fileManagerPrivate.FileTransferStatus} status Transfer
* status.
* @private
*/
removeItem_(status) {
this.queue_.run(callback => {
this.item_.state = status.transferState === 'completed' ?
async removeItem_(item, status) {
const unlock = await this.queue_.lock();
try {
item.state = status.transferState === 'completed' ?
ProgressItemState.COMPLETED :
ProgressItemState.CANCELED;
this.progressCenter_.updateItem(this.item_);
this.progressCenter_.updateItem(item);
this.syncing_ = false;
this.dispatchEvent(new Event(this.getCompletedEventName()));
callback();
});
} finally {
unlock();
}
}
/**
......@@ -321,8 +365,10 @@ class DriveSyncHandlerImpl extends cr.EventTarget {
if (state.type == 'offline' && state.reason == 'no_network' &&
this.syncing_) {
this.syncing_ = false;
this.item_.state = ProgressItemState.CANCELED;
this.progressCenter_.updateItem(this.item_);
this.syncItem_.state = ProgressItemState.CANCELED;
this.pinItem_.state = ProgressItemState.CANCELED;
this.progressCenter_.updateItem(this.syncItem_);
this.progressCenter_.updateItem(this.pinItem_);
this.dispatchEvent(new Event(this.getCompletedEventName()));
}
});
......
......@@ -29,6 +29,15 @@ mockChrome.fileManagerPrivate = {
},
listener_: null
},
onPinTransfersUpdated: {
addListener: function(callback) {
mockChrome.fileManagerPrivate.onPinTransfersUpdated.listener_ = callback;
},
removeListener: function() {
mockChrome.fileManagerPrivate.onPinTransfersUpdated.listener_ = null;
},
listener_: null
},
onDriveSyncError: {
addListener: function(callback) {
mockChrome.fileManagerPrivate.onDriveSyncError.listener_ = callback;
......@@ -182,19 +191,20 @@ function testErrorWithoutPath() {
}
// Test offline.
function testOffline() {
async function testOffline() {
// Start a transfer.
mockChrome.fileManagerPrivate.onFileTransfersUpdated.listener_({
await mockChrome.fileManagerPrivate.onFileTransfersUpdated.listener_({
fileUrl: 'name',
transferState: 'in_progress',
processed: 50.0,
total: 100.0,
numTotalJobs: 1,
num_total_jobs: 1,
hideWhenZeroJobs: true,
});
// Check that this created one item.
assertEquals(1, progressCenter.getItemCount());
// Check that this created one progressing item.
assertEquals(
1, progressCenter.getItemsByState(ProgressItemState.PROGRESSING).length);
let item = progressCenter.items['drive-sync'];
assertEquals(ProgressItemState.PROGRESSING, item.state);
assertTrue(driveSyncHandler.syncing);
......@@ -203,8 +213,79 @@ function testOffline() {
mockChrome.fileManagerPrivate.onDriveConnectionStatusChanged.listener_();
// Check that this item was cancelled.
assertEquals(1, progressCenter.getItemCount());
// There are two items cancelled including the pin item.
assertEquals(
2, progressCenter.getItemsByState(ProgressItemState.CANCELED).length);
item = progressCenter.items['drive-sync'];
assertEquals(ProgressItemState.CANCELED, item.state);
assertFalse(driveSyncHandler.syncing);
}
// Test transfer status updates.
async function testTransferUpdate() {
// Start a pin transfer.
await mockChrome.fileManagerPrivate.onPinTransfersUpdated.listener_({
fileUrl: 'name',
transferState: 'in_progress',
processed: 50.0,
total: 100.0,
num_total_jobs: 1,
hideWhenZeroJobs: true,
});
// There should be one progressing pin item and one canceled sync item.
assertEquals(2, progressCenter.getItemCount());
let syncItem = progressCenter.items['drive-sync'];
assertEquals(ProgressItemState.CANCELED, syncItem.state);
let pinItem = progressCenter.items['drive-pin'];
assertEquals(ProgressItemState.PROGRESSING, pinItem.state);
// Start a sync transfer.
await mockChrome.fileManagerPrivate.onFileTransfersUpdated.listener_({
fileUrl: 'name',
transferState: 'in_progress',
processed: 25.0,
total: 100.0,
num_total_jobs: 1,
hideWhenZeroJobs: true,
});
// There should be two progressing items.
assertEquals(2, progressCenter.getItemCount());
assertEquals(
2, progressCenter.getItemsByState(ProgressItemState.PROGRESSING).length);
// Finish the pin transfer.
await mockChrome.fileManagerPrivate.onPinTransfersUpdated.listener_({
fileUrl: 'name',
transferState: 'completed',
processed: 100.0,
total: 100.0,
num_total_jobs: 0,
hideWhenZeroJobs: true,
});
// There should be one completed pin item and one progressing sync item.
assertEquals(2, progressCenter.getItemCount());
syncItem = progressCenter.items['drive-sync'];
assertEquals(ProgressItemState.PROGRESSING, syncItem.state);
pinItem = progressCenter.items['drive-pin'];
assertEquals(ProgressItemState.COMPLETED, pinItem.state);
// Fail the sync transfer.
await mockChrome.fileManagerPrivate.onFileTransfersUpdated.listener_({
fileUrl: 'name',
transferState: 'failed',
processed: 40.0,
total: 100.0,
num_total_jobs: 0,
hideWhenZeroJobs: true,
});
// There should be one completed pin item and one canceled sync item.
assertEquals(2, progressCenter.getItemCount());
syncItem = progressCenter.items['drive-sync'];
assertEquals(ProgressItemState.CANCELED, syncItem.state);
pinItem = progressCenter.items['drive-pin'];
assertEquals(ProgressItemState.COMPLETED, pinItem.state);
}
......@@ -47,4 +47,13 @@ class MockProgressCenter {
/** @type {!Object} */ (this.items));
return array.length;
}
/**
* Returns the items that have a given state.
* @param {ProgressItemState} state State to filter by.
* @returns {!Array<ProgressCenterItem>}
*/
getItemsByState(state) {
return Object.values(this.items).filter(item => item.state == state);
}
}
......@@ -188,6 +188,7 @@ chrome.fileManagerPrivate = {
onDriveConnectionStatusChanged: new test.Event(),
onDriveSyncError: new test.Event(),
onFileTransfersUpdated: new test.Event(),
onPinTransfersUpdated: new test.Event(),
onMountCompleted: new test.Event(),
onPreferencesChanged: new test.Event(),
openInspector: (type) => {},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment