Skip to content

Instantly share code, notes, and snippets.

@BrandonSmith
Created June 6, 2017 14:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BrandonSmith/138957d6adadb7105395122358f19006 to your computer and use it in GitHub Desktop.
Save BrandonSmith/138957d6adadb7105395122358f19006 to your computer and use it in GitHub Desktop.
Fork of `electron-lets-move`
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