Created
June 6, 2017 14:54
-
-
Save BrandonSmith/138957d6adadb7105395122358f19006 to your computer and use it in GitHub Desktop.
Fork of `electron-lets-move`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
app, | |
dialog, | |
shell | |
} from 'electron' | |
import os from 'os' | |
import childProcess from 'child_process' | |
import sudo from 'sudo-prompt' | |
import path from 'path' | |
import Promise from './promise' | |
import fs from './fs' | |
import logger from './log' | |
import { isDebug } from '../common/debug' | |
import { isMac } from '../common/os-helpers' | |
const TAG = `MOVEIT` | |
const title = app.getName() | |
const exePath = app.getPath('exe') | |
const ROOT_APP_PATH = '/Applications' | |
const USER_APP_PATH = path.join(app.getPath('home'), 'Applications') | |
const USER_DOWNLOADS_PATH = app.getPath('downloads') | |
function getBundlePath() { | |
const bundleExtension = '.app' | |
const parts = exePath.split(bundleExtension) | |
return `${parts[0]}${bundleExtension}` | |
} | |
function canWriteTo(path) { | |
return fs.accessAsync(path, fs.W_OK) | |
.then(() => true) | |
.catch(() => false) | |
} | |
function isInApplicationsFolder() { | |
logger.debug(TAG, `exePath => ${exePath}`) | |
logger.debug(TAG, `ROOT_APP_PATH => ${ROOT_APP_PATH}`) | |
logger.debug(TAG, `USER_APP_PATH => ${USER_APP_PATH}`) | |
return exePath.startsWith(ROOT_APP_PATH) || exePath.startsWith(USER_APP_PATH) | |
} | |
function isInDownloadsFolder() { | |
const downloadsPath = | |
logger.debug(TAG, `exePath => ${exePath}`) | |
logger.debug(TAG, `USER_DOWNLOADS_PATH => ${USER_DOWNLOADS_PATH}`) | |
return exePath.startsWith(USER_DOWNLOADS_PATH) | |
} | |
function preferredInstallLocation() { | |
return ROOT_APP_PATH | |
} | |
function moveToTrash(directory) { | |
if (!fs.existsSync(directory)) return true | |
return shell.moveItemToTrash(directory) | |
} | |
function getDialogMessage(needsSudo) { | |
const msg = `I can move myself to the Applications folder if you'd like.` | |
if (needsSudo) { | |
return `${msg} Note that this will require an administrator password.` | |
// } else if (isInDownloadsFolder()) { | |
// return `${msg} This will keep your Downloads folder uncluttered.` | |
} else { | |
return msg | |
} | |
} | |
// execute command with optional sudo UI prompt | |
function exec(cmd, needsSudo) { | |
if (needsSudo) { | |
return Promise.promisify(sudo.exec)(cmd, { name: app.getName() }) | |
} else { | |
return Promise.promisify(childProcess.exec)(cmd) | |
} | |
} | |
function shouldBypassCheck() { | |
// don't run this logic in dev mode | |
if (isDebug) { | |
logger.debug(TAG, `isDebug`) | |
return true | |
} | |
// if we're not on macos, then we short circuit out of here | |
if (!isMac()) { | |
logger.debug(TAG, `is not macos`) | |
return true | |
} | |
// skip if the app is already in some applications folder | |
if (isInApplicationsFolder()) { | |
logger.debug(TAG, `already in applications folder`) | |
return true | |
} | |
return false | |
} | |
export function warnIfNotInPreferredLocation() { | |
return Promise.try(() => { | |
const bypass = shouldBypassCheck() | |
if (bypass) { | |
return false | |
} | |
// const bundlePath = getBundlePath() | |
const chosen = dialog.showMessageBox({ | |
type: `warning`, | |
buttons: [`Exit`, `Ignore`], | |
message: `Run from the Applications folder?`, | |
detail: `${title} can run from anywhere but prefers to run from the Applications folder. Please exit and move it there.` | |
}) | |
return chosen === 0 // did user choose exit | |
}) | |
} | |
/** | |
* Prompt the user when running on macos and not in preferred location | |
* If the user options to move the app, move and then | |
* @return {boolean} `true` is app should exit | |
* `false` if user opts to ignore warning | |
*/ | |
export function moveToApplications() { | |
return Promise.try(() => { | |
const bypass = shouldBypassCheck() | |
if (bypass) { | |
return false | |
} | |
const bundlePath = getBundlePath() | |
const fileName = path.basename(bundlePath) | |
const appDir = preferredInstallLocation() | |
const appPath = path.join(appDir, fileName) | |
logger.debug(TAG, `appPath => ${appPath}`) | |
// check if the install location needs sudo | |
return canWriteTo(appDir) | |
.then(isWritable => { | |
const needsSudo = !isWritable | |
// show dialog requesting to move | |
const detail = getDialogMessage(needsSudo) | |
const chosen = dialog.showMessageBox({ | |
type: 'question', | |
buttons: ['Move to Applications', 'Ignore'], | |
message: 'Move to Applications folder?', | |
detail | |
}) | |
// user chose to do nothing | |
if (chosen !== 0) { | |
return false | |
} | |
// move any existing application bundle located at target install location to the trash | |
if (!moveToTrash(appPath)) { | |
// if failure | |
dialog.showMessageBox({ | |
type: 'error', | |
title, | |
buttons: ['OK'], | |
message: `Failed to move ${title} to the Applications folder.`, | |
detail: `Failed to move existing application to Trash. It may be in use or you may not have sufficient permission to move it.` | |
}) | |
// quit this app instance immediately | |
app.exit(0) | |
} | |
return exec(`cp -R "${bundlePath}" "${appPath}"`, needsSudo) // copy app to target location | |
.then(() => { | |
// open the moved app | |
const execName = path.basename(process.execPath) | |
const execPath = path.join(appPath, 'Contents', 'MacOS', execName) | |
// start copied app completely independent from this app instance | |
const child = childProcess.spawn(execPath, [], { detached: true, stdio: 'ignore' }) | |
child.unref() | |
// quit this app instance immediately | |
app.exit(0) | |
}) | |
}) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment