Commit 3619cfab authored by Joel Hockey's avatar Joel Hockey Committed by Commit Bot

Show retry dialog when Plugin VM unsharing folder fails

Unsharing can fail if the VM has a file open within a shared folder.
In that case, we show a dialog to notify the user that unsharing failed
and allow them to retry.

https://storage.cloud.google.com/chromium-translation-screenshots/ec0fdd89ab5d4afc79c7e65d463ecece4925ac68

Bug: 920416
Change-Id: Ib47a7ca6a1d3a8b9501613a36b211fcc5a70d1f4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2413428
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarRegan Hsu <hsuregan@chromium.org>
Auto-Submit: Joel Hockey <joelhockey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#808159}
parent b7eb25b4
...@@ -3081,6 +3081,15 @@ ...@@ -3081,6 +3081,15 @@
<message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING" desc="Tooltip to show when hovering on the remove icon for a Parallels shared folder."> <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING" desc="Tooltip to show when hovering on the remove icon for a Parallels shared folder.">
Stop sharing Stop sharing
</message> </message>
<message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE" desc="Message to show user when unsharing a Parallels shared folder fails. Do not translate 'Parallels Desktop'">
Couldn't unshare because an application is using this folder. The folder will be unshared when Parallels Desktop is next shut down.
</message>
<message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE" desc="Title of the error dialog shown to a user when unsharing a Parallels shared folder fails.">
Unshare failed
</message>
<message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN" desc="Button text in the dialog shown when unsharing a Parallels shared folder fails. Pressing this button will attempt to unshare the folder again.">
Try again
</message>
<message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE" desc="Message shown when the user has not yet shared any folders in Parallels."> <message name="IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE" desc="Message shown when the user has not yet shared any folders in Parallels.">
Shared folders will appear here Shared folders will appear here
</message> </message>
......
...@@ -41,6 +41,7 @@ cr.define('settings', function() { ...@@ -41,6 +41,7 @@ cr.define('settings', function() {
/** /**
* @param {string} vmName VM to stop sharing path with. * @param {string} vmName VM to stop sharing path with.
* @param {string} path Path to stop sharing. * @param {string} path Path to stop sharing.
* @return {!Promise<boolean>} Result of unsharing.
*/ */
removePluginVmSharedPath(vmName, path) {} removePluginVmSharedPath(vmName, path) {}
...@@ -82,7 +83,7 @@ cr.define('settings', function() { ...@@ -82,7 +83,7 @@ cr.define('settings', function() {
/** @override */ /** @override */
removePluginVmSharedPath(vmName, path) { removePluginVmSharedPath(vmName, path) {
chrome.send('removePluginVmSharedPath', [vmName, path]); return cr.sendWithPromise('removePluginVmSharedPath', vmName, path);
} }
/** @override */ /** @override */
......
...@@ -42,6 +42,27 @@ ...@@ -42,6 +42,27 @@
</template> </template>
</iron-list> </iron-list>
</div> </div>
<template is="dom-if" if="[[sharedPathWhichFailedRemoval_]]" restamp>
<cr-dialog id="removeSharedPathFailedDialog" close-text="$i18n{close}"
show-on-attach>
<div slot="title">
$i18n{pluginVmSharedPathsRemoveFailureDialogTitle}
</div>
<div slot="body">
$i18n{pluginVmSharedPathsRemoveFailureDialogMessage}
</div>
<div slot="button-container">
<cr-button id="cancel" class="cancel-button"
on-click="onRemoveFailedDismissClick_">
$i18n{ok}
</cr-button>
<cr-button id="retry" class="action-button"
on-click="onRemoveFailedRetryClick_">
$i18n{pluginVmSharedPathsRemoveFailureTryAgain}
</cr-button>
</div>
</cr-dialog>
</template>
</template> </template>
<script src="plugin_vm_shared_paths.js"></script> <script src="plugin_vm_shared_paths.js"></script>
</dom-module> </dom-module>
...@@ -31,6 +31,17 @@ Polymer({ ...@@ -31,6 +31,17 @@ Polymer({
* @private {Array<!{path: string, pathDisplayText: string}>} * @private {Array<!{path: string, pathDisplayText: string}>}
*/ */
sharedPaths_: Array, sharedPaths_: Array,
/**
* The shared path which failed to be removed in the most recent attempt
* to remove a path. Null indicates that removal succeeded. When non-null,
* the failure dialog is shown.
* @private {?string}
*/
sharedPathWhichFailedRemoval_: {
type: String,
value: null,
},
}, },
observers: [ observers: [
...@@ -57,14 +68,38 @@ Polymer({ ...@@ -57,14 +68,38 @@ Polymer({
}); });
}, },
/**
* @param {string} path
* @private
*/
removeSharedPath_(path) {
this.sharedPathWhichFailedRemoval_ = null;
settings.PluginVmBrowserProxyImpl.getInstance()
.removePluginVmSharedPath(PLUGIN_VM, path)
.then(success => {
if (!success) {
this.sharedPathWhichFailedRemoval_ = path;
}
});
settings.recordSettingChange();
},
/** /**
* @param {!Event} event * @param {!Event} event
* @private * @private
*/ */
onRemoveSharedPathClick_(event) { onRemoveSharedPathClick_(event) {
settings.PluginVmBrowserProxyImpl.getInstance().removePluginVmSharedPath( this.removeSharedPath_(event.model.item.path);
PLUGIN_VM, event.model.item.path); },
settings.recordSettingChange();
/** @private */
onRemoveFailedRetryClick_() {
this.removeSharedPath_(assert(this.sharedPathWhichFailedRemoval_));
},
/** @private */
onRemoveFailedDismissClick_() {
this.sharedPathWhichFailedRemoval_ = null;
}, },
}); });
})(); })();
...@@ -326,6 +326,12 @@ void AppsSection::AddPluginVmLoadTimeData( ...@@ -326,6 +326,12 @@ void AppsSection::AddPluginVmLoadTimeData(
IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE}, IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_INSTRUCTIONS_REMOVE},
{"pluginVmSharedPathsRemoveSharing", {"pluginVmSharedPathsRemoveSharing",
IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING}, IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_SHARING},
{"pluginVmSharedPathsRemoveFailureDialogMessage",
IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_MESSAGE},
{"pluginVmSharedPathsRemoveFailureDialogTitle",
IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_DIALOG_TITLE},
{"pluginVmSharedPathsRemoveFailureTryAgain",
IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_REMOVE_FAILURE_TRY_AGAIN},
{"pluginVmSharedPathsListEmptyMessage", {"pluginVmSharedPathsListEmptyMessage",
IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE}, IDS_SETTINGS_APPS_PLUGIN_VM_SHARED_PATHS_LIST_EMPTY_MESSAGE},
{"pluginVmSharedUsbDevicesLabel", {"pluginVmSharedUsbDevicesLabel",
......
...@@ -104,22 +104,27 @@ void PluginVmHandler::HandleGetPluginVmSharedPathsDisplayText( ...@@ -104,22 +104,27 @@ void PluginVmHandler::HandleGetPluginVmSharedPathsDisplayText(
void PluginVmHandler::HandleRemovePluginVmSharedPath( void PluginVmHandler::HandleRemovePluginVmSharedPath(
const base::ListValue* args) { const base::ListValue* args) {
CHECK_EQ(2U, args->GetSize()); CHECK_EQ(3U, args->GetList().size());
std::string vm_name = args->GetList()[0].GetString(); std::string callback_id = args->GetList()[0].GetString();
std::string path = args->GetList()[1].GetString(); std::string vm_name = args->GetList()[1].GetString();
std::string path = args->GetList()[2].GetString();
guest_os::GuestOsSharePath::GetForProfile(profile_)->UnsharePath( guest_os::GuestOsSharePath::GetForProfile(profile_)->UnsharePath(
vm_name, base::FilePath(path), vm_name, base::FilePath(path),
/*unpersist=*/true, /*unpersist=*/true,
base::BindOnce( base::BindOnce(&PluginVmHandler::OnPluginVmSharedPathRemoved,
[](const std::string& path, bool result, weak_ptr_factory_.GetWeakPtr(), callback_id, path));
const std::string& failure_reason) { }
if (!result) {
LOG(ERROR) << "Error unsharing " << path << ": " void PluginVmHandler::OnPluginVmSharedPathRemoved(
<< failure_reason; const std::string& callback_id,
} const std::string& path,
}, bool success,
path)); const std::string& failure_reason) {
if (!success) {
LOG(ERROR) << "Error unsharing " << path << ": " << failure_reason;
}
ResolveJavascriptCallback(base::Value(callback_id), base::Value(success));
} }
void PluginVmHandler::HandleNotifyPluginVmSharedUsbDevicesPageReady( void PluginVmHandler::HandleNotifyPluginVmSharedUsbDevicesPageReady(
......
...@@ -52,6 +52,11 @@ class PluginVmHandler : public ::settings::SettingsPageUIHandler, ...@@ -52,6 +52,11 @@ class PluginVmHandler : public ::settings::SettingsPageUIHandler,
// Relaunches Plugin VM. // Relaunches Plugin VM.
void HandleRelaunchPluginVm(const base::ListValue* args); void HandleRelaunchPluginVm(const base::ListValue* args);
void OnPluginVmSharedPathRemoved(const std::string& callback_id,
const std::string& path,
bool success,
const std::string& failure_reason);
Profile* profile_; Profile* profile_;
// weak_ptr_factory_ should always be last member. // weak_ptr_factory_ should always be last member.
......
...@@ -9,6 +9,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy { ...@@ -9,6 +9,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy {
'getPluginVmSharedPathsDisplayText', 'getPluginVmSharedPathsDisplayText',
'removePluginVmSharedPath', 'removePluginVmSharedPath',
]); ]);
this.removeSharedPathResult = true;
} }
/** override */ /** override */
...@@ -20,6 +21,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy { ...@@ -20,6 +21,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy {
/** override */ /** override */
removePluginVmSharedPath(vmName, path) { removePluginVmSharedPath(vmName, path) {
this.methodCalled('removePluginVmSharedPath', [vmName, path]); this.methodCalled('removePluginVmSharedPath', [vmName, path]);
return Promise.resolve(this.removeSharedPathResult);
} }
} }
...@@ -94,4 +96,25 @@ suite('SharedPaths', function() { ...@@ -94,4 +96,25 @@ suite('SharedPaths', function() {
assertTrue(page.$.pluginVmList.hidden); assertTrue(page.$.pluginVmList.hidden);
assertFalse(page.$.pluginVmListEmpty.hidden); assertFalse(page.$.pluginVmListEmpty.hidden);
}); });
test('RemoveFailedRetry', async function() {
await setPrefs({'path1': ['PvmDefault'], 'path2': ['PvmDefault']});
// Remove shared path fails.
pluginVmBrowserProxy.removeSharedPathResult = false;
page.$$('.list-item cr-icon-button').click();
await pluginVmBrowserProxy.whenCalled('removePluginVmSharedPath');
Polymer.dom.flush();
assertTrue(page.$$('#removeSharedPathFailedDialog').open);
// Click retry and make sure 'removePluginVmSharedPath' is called
// and dialog is closed/removed.
pluginVmBrowserProxy.removeSharedPathResult = true;
page.$$('#removeSharedPathFailedDialog')
.querySelector('.action-button')
.click();
await pluginVmBrowserProxy.whenCalled('removePluginVmSharedPath');
assertFalse(!!page.$$('#removeSharedPathFailedDialog'));
});
}); });
...@@ -12,6 +12,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy { ...@@ -12,6 +12,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy {
'setPluginVmPermission', 'setPluginVmPermission',
'relaunchPluginVm', 'relaunchPluginVm',
]); ]);
this.removeSharedPathResult = true;
this.pluginVmRunning = false; this.pluginVmRunning = false;
this.permissions = [true, true]; // [0]Camera, [1]Microphone this.permissions = [true, true]; // [0]Camera, [1]Microphone
} }
...@@ -25,6 +26,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy { ...@@ -25,6 +26,7 @@ class TestPluginVmBrowserProxy extends TestBrowserProxy {
/** @override */ /** @override */
removePluginVmSharedPath(vmName, path) { removePluginVmSharedPath(vmName, path) {
this.methodCalled('removePluginVmSharedPath', vmName, path); this.methodCalled('removePluginVmSharedPath', vmName, path);
return Promise.resolve(this.removeSharedPathResult);
} }
/** @override */ /** @override */
......
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