feat: app.moveToApplicationsFolder conflict handling (#18916)

Resolves #18805.

We want to keep default move conflict handling behavior in that it's still what most users would expect, but there exist edge cases in which users may not want to be forced into that behavior.

This thus introduces an optional conflict handler that allows developers access to more granular move actions. They could now allow the user to choose whether to delete an existing app in favor of the current one being moved, or whether to quit the current app and focus on the existing one should it both exist and be running. I added a fair amount of new documentation outlining this behavior, but if there are things users may benefit from seeing examples of or nuances that should be added please leave feedback!
This commit is contained in:
Shelley Vohr 2019-07-15 09:34:20 -07:00 committed by GitHub
parent 0db6789210
commit f6a29707b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 9 deletions

View file

@ -1430,7 +1430,6 @@ void App::BuildPrototype(v8::Isolate* isolate,
base::BindRepeating(&Browser::ResignCurrentActivity, browser))
.SetMethod("updateCurrentActivity",
base::BindRepeating(&Browser::UpdateCurrentActivity, browser))
// TODO(juturu): Remove in 2.0, deprecate before then with warnings
.SetMethod("moveToApplicationsFolder", &App::MoveToApplicationsFolder)
.SetMethod("isInApplicationsFolder", &App::IsInApplicationsFolder)
#endif

View file

@ -11,6 +11,9 @@
namespace electron {
// Possible bundle movement conflicts
enum class BundlerMoverConflictType { EXISTS, EXISTS_AND_RUNNING };
namespace ui {
namespace cocoa {
@ -21,6 +24,8 @@ class AtomBundleMover {
static bool IsCurrentAppInApplicationsFolder();
private:
static bool ShouldContinueMove(BundlerMoverConflictType type,
mate::Arguments* args);
static bool IsInApplicationsFolder(NSString* bundlePath);
static NSString* ContainingDiskImageDevice(NSString* bundlePath);
static void Relaunch(NSString* destinationPath);

View file

@ -14,6 +14,26 @@
#import <sys/param.h>
#import "shell/browser/browser.h"
#include "shell/common/native_mate_converters/once_callback.h"
namespace mate {
template <>
struct Converter<electron::BundlerMoverConflictType> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
electron::BundlerMoverConflictType value) {
switch (value) {
case electron::BundlerMoverConflictType::EXISTS:
return mate::StringToV8(isolate, "exists");
case electron::BundlerMoverConflictType::EXISTS_AND_RUNNING:
return mate::StringToV8(isolate, "existsAndRunning");
default:
return mate::StringToV8(isolate, "");
}
}
};
} // namespace mate
namespace electron {
@ -21,6 +41,28 @@ namespace ui {
namespace cocoa {
bool AtomBundleMover::ShouldContinueMove(BundlerMoverConflictType type,
mate::Arguments* args) {
mate::Dictionary options;
bool hasOptions = args->GetNext(&options);
base::OnceCallback<v8::Local<v8::Value>(BundlerMoverConflictType)>
conflict_cb;
if (hasOptions && options.Get("conflictHandler", &conflict_cb)) {
v8::Local<v8::Value> value = std::move(conflict_cb).Run(type);
if (value->IsBoolean()) {
if (!value.As<v8::Boolean>()->Value())
return false;
} else if (!value->IsUndefined()) {
// we only want to throw an error if a user has returned a non-boolean
// value; this allows for client-side error handling should something in
// the handler throw
args->ThrowError("Invalid conflict handler return type.");
}
}
return true;
}
bool AtomBundleMover::Move(mate::Arguments* args) {
// Path of the current bundle
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
@ -74,7 +116,12 @@ bool AtomBundleMover::Move(mate::Arguments* args) {
if ([fileManager fileExistsAtPath:destinationPath]) {
// But first, make sure that it's not running
if (IsApplicationAtPathRunning(destinationPath)) {
// Give the running app focus and terminate myself
// Check for callback handler and get user choice for open/quit
if (!ShouldContinueMove(BundlerMoverConflictType::EXISTS_AND_RUNNING,
args))
return false;
// Unless explicitly denied, give running app focus and terminate self
[[NSTask
launchedTaskWithLaunchPath:@"/usr/bin/open"
arguments:[NSArray
@ -83,6 +130,11 @@ bool AtomBundleMover::Move(mate::Arguments* args) {
electron::Browser::Get()->Quit();
return true;
} else {
// Check callback handler and get user choice for app trashing
if (!ShouldContinueMove(BundlerMoverConflictType::EXISTS, args))
return false;
// Unless explicitly denied, attempt to trash old app
if (!Trash([applicationsDirectory
stringByAppendingPathComponent:bundleName])) {
args->ThrowError("Failed to delete existing application");
@ -313,11 +365,7 @@ bool AtomBundleMover::CopyBundle(NSString* srcPath, NSString* dstPath) {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSError* error = nil;
if ([fileManager copyItemAtPath:srcPath toPath:dstPath error:&error]) {
return true;
} else {
return false;
}
return [fileManager copyItemAtPath:srcPath toPath:dstPath error:&error];
}
NSString* AtomBundleMover::ShellQuotedString(NSString* string) {