Skip to content

Commit

Permalink
gui/main: Clean old Windows Uninstall registry key (#1755)
Browse files Browse the repository at this point in the history
* core/app: Separate setup from sync start

  We want to be able to run actions as soon as the app is setup but
  before we've started synchronizing (or rather, while we start
  synchronizing) so we don't "waste" time.
  Besides, when running a manual synchronization, we shouldn't have to
  re-instantiate the whole app!

  We can easily separate the app setup from its synchronization start
  via a new method.

  We can take this opportunity to better handle configuration issues,
  especially in the way we alert the user about it.

* gui/main: Update systray state before window state

  The systray icon's state will be the first thing users will see in
  most situations since the app window is hidden most of the time.
  Therefore, updating its state first seems more appropriate especially
  if the window state update is blocking (i.e. requires user interaction
  like when we detect an invalid configuration).

* gui/main: Clean old Windows Uninstall registry key

  Windows uses Uninstall registry subkeys to know where to find the
  uninstall executables of applications and list them in its programs
  management application.

  In the more recent versions of `electron-builder`, the format of the
  subkey has changed and `electron-builder` does not clean up old
  subkeys after an update.
  This means that users updating Cozy Desktop to v3.16.0 will see 2
  Cozy Desktop applications listed in the programs management
  application and trying to uninstall the oldest version will actually
  uninstall the most recent (the uninstall paths are the same) and leave
  the old entry without any way to remove it.

  We introduce here a registry cleanup during the app startup phase so
  that our users won't have to manually edit their registry to remove
  the old subkey.
  • Loading branch information
taratatach authored Nov 6, 2019
2 parents bca13c7 + 61ff417 commit d2483c3
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 23 deletions.
12 changes: 3 additions & 9 deletions core/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,19 +320,13 @@ class App {
return this.sync.stop()
}

// Start database sync process and setup file change watcher
synchronize(mode /*: SyncMode */) {
log.info(this.clientInfo(), 'synchronize')
setup() {
log.info(this.clientInfo(), 'user config')
sentry.setup(this.clientInfo())
if (!this.config.isValid()) {
log.error(
'No configuration found, please run add-remote-cozy' +
'command before running a synchronization.'
)
throw new Error('No client configured')
throw new config.InvalidConfigError()
}
this.instanciate()
return this.startSync(mode)
}

clientInfo() {
Expand Down
9 changes: 9 additions & 0 deletions core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export type SyncMode = 'full' | 'pull' | 'push'
type FileConfig = Object
*/

const INVALID_CONFIG_ERROR = 'InvalidConfigError'
const INVALID_CONFIG_MESSAGE = 'Invalid client configuration'
function InvalidConfigError() {
this.name = INVALID_CONFIG_ERROR
this.message = INVALID_CONFIG_MESSAGE
}

// Config can keep some configuration parameters in a JSON file,
// like the devices credentials or the mount path
class Config {
Expand Down Expand Up @@ -264,6 +271,8 @@ function validateWatcherType(watcherType /*: ?string */) /*: ?WatcherType */ {
}

module.exports = {
INVALID_CONFIG_ERROR,
InvalidConfigError,
Config,
environmentWatcherType,
load,
Expand Down
48 changes: 48 additions & 0 deletions core/utils/win_registry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Windows uses Uninstall registry subkeys to know where to find the
* uninstall executables of applications and list them in its programs
* management application.
* In the more recent versions of `electron-builder`, the format of the
* subkey has changed and `electron-builder` does not clean up old
* subkeys after an update.
* This means that users updating Cozy Desktop to v3.16.0 will see 2
* Cozy Desktop applications listed in the programs management
* application and trying to uninstall the oldest version will actually
* uninstall the most recent (the uninstall paths are the same) and leave
* the old entry without any way to remove it.
* We introduce here a registry cleanup during the app startup phase so
* that our users won't have to manually edit their registry to remove
* the old subkey.
*/

const regedit = require('regedit')

// Key used prior to v3.16.0
const OLD_UNINSTALL_KEY =
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\4e3f3566-be06-5f9a-b012-0cf924cd77aa'

const REGEDIT_INEXISTANT_PATH_ERROR_CODE = 2
const REGEDIT_ERROR = 'RegeditError'
function RegeditError(code, msg) {
this.name = REGEDIT_ERROR
this.code = code
this.message = msg
}

async function removeOldUninstallKey() {
return new Promise((resolve, reject) => {
regedit.deleteKey(OLD_UNINSTALL_KEY, err => {
if (err && err.code !== REGEDIT_INEXISTANT_PATH_ERROR_CODE) {
reject(new RegeditError(err.code, err.message))
}
resolve()
})
})
}

module.exports = {
RegeditError,
removeOldUninstallKey
}
2 changes: 1 addition & 1 deletion gui/js/tray.window.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ module.exports = class TrayWM extends WindowManager {
'unlink-cozy': this.onUnlink,
'manual-start-sync': () =>
this.desktop.stopSync().then(() => {
this.desktop.synchronize('full')
this.desktop.startSync('full')
})
}
}
Expand Down
5 changes: 5 additions & 0 deletions gui/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@
"Helpers Just now": "Just now",
"Helpers KB": "KB",
"Helpers MB": "MB",
"InvalidConfiguration Invalid configuration": "Invalid configuration",
"InvalidConfiguration The client configuration is invalid": "The client configuration is invalid",
"InvalidConfiguration Please log out and go through the onboarding again or contact us at [email protected]": "Please log out and go through the onboarding again or contact us at [email protected]",
"InvalidConfiguration Log out": "Log out",
"InvalidConfiguration Contact support": "Contact support",
"Password Login": "Login",
"Password Password": "Password",
"Password Wrong cozy address?": "Wrong cozy address?",
Expand Down
5 changes: 5 additions & 0 deletions gui/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@
"Helpers Just now": "Juste à l’instant",
"Helpers KB": "Ko",
"Helpers MB": "Mo",
"InvalidConfiguration Invalid configuration": "Configuration invalide",
"InvalidConfiguration The client configuration is invalid": "La configuration du client est invalide",
"InvalidConfiguration Please log out and go through the onboarding again or contact us at [email protected]": "Veuillez vous déconnecter et réinitialiser votre client ou contactez nous sur [email protected]",
"InvalidConfiguration Log out": "Se déconnecter",
"InvalidConfiguration Contact support": "Contacter le support",
"Password Login": "Se connecter",
"Password Password": "Mot de passe",
"Password Wrong cozy address?": "Mauvaise adresse Cozy ?",
Expand Down
84 changes: 72 additions & 12 deletions gui/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const os = require('os')
const proxy = require('./js/proxy')
const { COZY_CLIENT_REVOKED_MESSAGE } = require('../core/remote/cozy')
const migrations = require('../core/pouch/migrations')
const config = require('../core/config')
const winRegistry = require('../core/utils/win_registry')

const autoLaunch = require('./js/autolaunch')
const lastFiles = require('./js/lastfiles')
Expand Down Expand Up @@ -87,6 +89,7 @@ const showWindow = bounds => {

let revokedAlertShown = false
let syncDirUnlinkedShown = false
let invalidConfigShown = false

const sendErrorToMainWindow = msg => {
if (msg === COZY_CLIENT_REVOKED_MESSAGE) {
Expand Down Expand Up @@ -145,6 +148,40 @@ const sendErrorToMainWindow = msg => {
.then(() => trayWindow.doRestart())
.catch(err => log.error(err))
return // no notification
} else if (msg === config.INVALID_CONFIG_ERROR) {
msg = translate('InvalidConfiguration Invalid configuration')
trayWindow.send('sync-error', msg)

if (invalidConfigShown) return
invalidConfigShown = true // prevent the alert from appearing twice

const options = {
type: 'warning',
title: translate('InvalidConfiguration Invalid configuration'),
message: translate(
'InvalidConfiguration The client configuration is invalid'
),
detail: translate(
'InvalidConfiguration Please log out and go through the onboarding again or contact us at [email protected]'
),
buttons: [
translate('InvalidConfiguration Log out'),
translate('InvalidConfiguration Contact support')
],
defaultId: 0
}
trayWindow.hide()
const userChoice = dialog.showMessageBox(null, options)
if (userChoice === 0) {
desktop
.removeConfig()
.then(() => log.info('removed'))
.then(() => trayWindow.doRestart())
.catch(err => log.error(err))
} else {
helpWindow.show()
}
return // no notification
} else if (msg === 'Cozy is full' || msg === 'No more disk space') {
msg = translate('Error ' + msg)
trayWindow.send('sync-error', msg)
Expand Down Expand Up @@ -176,28 +213,28 @@ const updateState = (newState, data) => {
if (newState === 'online' && state !== 'offline') return
if (newState === 'offline' && state === 'error') return

// Update window status bar
if (newState === 'online') {
tray.setState('up-to-date')
trayWindow.send('up-to-date')
} else if (newState === 'offline') {
tray.setState('offline')
trayWindow.send('offline')
} else if (newState === 'error') {
tray.setState('error', data)
sendErrorToMainWindow(data)
} else if (newState === 'sync-status') {
tray.setState(data.label === 'uptodate' ? 'up-to-date' : 'syncing')
trayWindow.send('sync-status', data)
} else if (newState === 'syncing' && data && data.filename) {
tray.setState('syncing', data)
trayWindow.send('transfer', data)
}

if (newState === 'sync-status') {
state = data.label === 'uptodate' ? 'up-to-date' : 'syncing'
} else if (newState === 'uptodate') {
state = 'up-to-date'
} else {
state = newState
}
// Update systray icon and tooltip
tray.setState(state, data)
}

const addFile = info => {
Expand Down Expand Up @@ -308,8 +345,33 @@ const startSync = force => {
sendErrorToMainWindow('Syncdir has been unlinked')
})
desktop.events.on('delete-file', removeFile)

try {
desktop.setup()
} catch (err) {
log.fatal({ err, sentry: true }, 'Could not setup app')
if (err instanceof config.InvalidConfigError) {
updateState('error', err.name)
} else {
updateState('error', err.message)
}
return
}

// We do it here since Sentry's setup happens in `desktop.setup()`
if (process.platform === 'win32') {
winRegistry.removeOldUninstallKey().catch(err => {
if (err instanceof winRegistry.RegeditError) {
log.error(
{ err, sentry: true },
'Failed to remove uninstall registry key'
)
}
})
}

desktop
.synchronize(desktop.config.fileConfig.mode)
.startSync(desktop.config.fileConfig.mode)
.then(() => sendErrorToMainWindow('stopped'))
.catch(err => {
if (err.status === 402) {
Expand All @@ -327,13 +389,11 @@ const startSync = force => {
trayWindow.send('user-action-required', userActionRequired)
desktop.remote.warningsPoller.switchMode('medium')
return
} else if (err instanceof migrations.MigrationFailedError) {
updateState('error', err.name)
} else {
updateState('error', err.message)
}

const msg =
err instanceof migrations.MigrationFailedError
? err.name
: err.message
updateState('error', msg)
sendDiskUsage()
})
sendDiskUsage()
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"pouchdb": "^7.0.0",
"pouchdb-find": "^7.0.0",
"read": "1.0.7",
"regedit": "^3.0.3",
"trash": "^5.0.0",
"uuid": "^3.3.2",
"yargs": "^11.0.0"
Expand Down
40 changes: 39 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3278,6 +3278,11 @@ iconv-lite@^0.5.0:
dependencies:
safer-buffer ">= 2.1.2 < 3"

if-async@^3.7.4:
version "3.7.4"
resolved "https://registry.yarnpkg.com/if-async/-/if-async-3.7.4.tgz#55868deb0093d3c67bf7166e745353fb9bcb21a2"
integrity sha1-VYaN6wCT08Z79xZudFNT+5vLIaI=

ignore@^3.3.5:
version "3.3.10"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
Expand Down Expand Up @@ -5836,6 +5841,16 @@ readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stre
string_decoder "^1.1.1"
util-deprecate "^1.0.1"

"readable-stream@>=1.0.33-1 <1.1.0-0":
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"

readable-stream@~0.0.2:
version "0.0.4"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d"
Expand Down Expand Up @@ -5872,6 +5887,16 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"

regedit@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/regedit/-/regedit-3.0.3.tgz#0c2188e15f670de7d5740c5cea9bbebe99497749"
integrity sha512-SpHmMKOtiEYx0MiRRC48apBsmThoZ4svZNsYoK8leHd5bdUHV1nYb8pk8gh6Moou7/S9EDi1QsjBTpyXVQrPuQ==
dependencies:
debug "^4.1.0"
if-async "^3.7.4"
stream-slicer "0.0.6"
through2 "^0.6.3"

regenerator-runtime@^0.10.5:
version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
Expand Down Expand Up @@ -6519,6 +6544,11 @@ stealthy-require@^1.1.1:
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=

[email protected]:
version "0.0.6"
resolved "https://registry.yarnpkg.com/stream-slicer/-/stream-slicer-0.0.6.tgz#f86b2ac5c2440b7a0a87b71f33665c0788046138"
integrity sha1-+GsqxcJEC3oKh7cfM2ZcB4gEYTg=

strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
Expand Down Expand Up @@ -6809,6 +6839,14 @@ [email protected]:
dependencies:
readable-stream "2 || 3"

through2@^0.6.3:
version "0.6.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=
dependencies:
readable-stream ">=1.0.33-1 <1.1.0-0"
xtend ">=4.0.0 <4.1.0-0"

through2@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
Expand Down Expand Up @@ -7362,7 +7400,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=

xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0:
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
Expand Down

0 comments on commit d2483c3

Please sign in to comment.