Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto update the app #256

Merged
merged 4 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"vuetify": "^2.2.22",
"vuex": "^3.1.3",
"vuex-persist": "^2.2.0",
"ynab": "^1.19.0"
"ynab": "^1.19.0",
"electron-updater": "^4.3.9"
},
"devDependencies": {
"@babel/core": "^7.9.0",
Expand Down Expand Up @@ -114,4 +115,4 @@
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.4.4"
}
}
}
4 changes: 2 additions & 2 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
import logger from './logging/logger';
import Sentry from './logging/sentry';
import initializeHandlers from './handlers';
import { registerHandlers } from './handlers';
// import './store';

Sentry.initializeReporter();
Expand Down Expand Up @@ -43,7 +43,7 @@ function createWindow() {
}

// initialize electron event handlers
initializeHandlers();
registerHandlers();

mainWindow.on('closed', () => {
mainWindow = null;
Expand Down
29 changes: 18 additions & 11 deletions src/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { ipcMain, dialog, ipcRenderer } from 'electron';
import { checkForUpdate, downloadUpdate, quitAndInstall } from './updater';

export default function initialize() {
ipcMain.handle(SelectDirHandler.name, SelectDirHandler.handler);
}

export const SelectDirHandler = {
name: 'SELECT_DIRECTORY_FOLDER',
async handler() {
const functions = {
showSaveDialog: async () => {
const dir = await dialog.showSaveDialog({});

return dir.filePath;
},
invoke() {
return ipcRenderer.invoke(this.name);
},
checkForUpdate,
downloadUpdate,
quitAndInstall
};
type Functions = typeof functions;

export const ipcHandlers = Object.keys(functions).reduce((acc, funcName) => {
acc[funcName] = () => ipcRenderer.invoke(funcName);
return acc;
}, {} as Functions);

export const registerHandlers = () => {
Object.keys(functions).forEach((funcName) => {
ipcMain.handle(funcName, functions[funcName]);
});
};
Comment on lines 1 to 24
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baaraak See the approach of this file...

27 changes: 27 additions & 0 deletions src/handlers/updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import logger from '../logging/logger';

export type UpdateInfo = {
version: string
}

const isRenderer = (process && process.type === 'renderer');
// There is a typing issue with 'electron-updater' I can't solve
const autoUpdater = isRenderer ? {} : require('electron-updater').autoUpdater; // eslint-disable-line

autoUpdater.logger = logger;
autoUpdater.autoDownload = false;

export const checkForUpdate = async () => new Promise<UpdateInfo | false>((resolve, reject) => {
autoUpdater.once('error', reject);
autoUpdater.once('update-available', (info: UpdateInfo) => resolve(info));
autoUpdater.once('update-not-available', () => resolve(false));
autoUpdater.checkForUpdates();
});

export const downloadUpdate = async () => new Promise<any>((resolve, reject) => {
autoUpdater.once('error', reject);
autoUpdater.once('update-downloaded', resolve);
autoUpdater.downloadUpdate();
});

export const quitAndInstall = () => setImmediate(() => autoUpdater.quitAndInstall());
4 changes: 3 additions & 1 deletion src/ui/components/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
app
color="primary"
>
<Updater />
<v-spacer />
<v-btn
text
Expand All @@ -71,12 +72,13 @@ import Exporters from '@/ui/components/app/Exporters';
import { trackPage } from '@/logging/analytics';
import ReportProblemDialog from './app/ReportProblemDialog';
import MainContent from './app/MainContent';
import Updater from './app/Updater';
import { repository } from '../../../package.json';

export default {
name: 'App',
components: {
Importers, MainContent, Exporters, ReportProblemDialog,
Importers, MainContent, Exporters, ReportProblemDialog, Updater
},
data() {
return {
Expand Down
128 changes: 128 additions & 0 deletions src/ui/components/app/Updater.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<template>
<div
v-if="state === STATES.LOADING"
class="progress"
>
<v-progress-linear
:indeterminate="true"
class="mr-2"
color="black lighten-2"
/>
</div>
<v-btn
v-else-if="state === STATES.INIT"
text
small
@click="checkUpdates"
>
<span class="mr-2">Check for updates</span>
</v-btn>
<div v-else-if="state === STATES.NO_NEW_VERSION">
No update available
</div>
<div v-else-if="state === STATES.NEW_VERSION_AVAIL">
<v-btn
text
small
@click="downloadNewVersion"
>
Download version {{ updateInfo.version }}
</v-btn>
<v-btn
text
small
@click="openGithubRelease"
>
Github
<v-icon>mdi-open-in-new</v-icon>
</v-btn>
<v-btn
text
small
@click="openCompare"
>
Compare
<v-icon>mdi-open-in-new</v-icon>
</v-btn>
</div>
<v-btn
v-else-if="state === STATES.READY_TO_INSTALL"
text
small
@click="quitAndInstall"
>
Quit and install
</v-btn>
<div v-else>
Error during update
</div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';
import { ipcHandlers } from '@/handlers';
import { UpdateInfo } from '@/handlers/updater';
import { App } from '@/app-globals';
import { shell } from 'electron';
import { repository } from '../../../../package.json';

enum STATES {
INIT,
LOADING,
ERROR,
NEW_VERSION_AVAIL,
NO_NEW_VERSION,
READY_TO_INSTALL,
}

const currentVersion = App.getVersion();

export default defineComponent({
setup() {
const state = ref(STATES.INIT);
const updateInfo = ref<UpdateInfo|undefined>();

const checkUpdates = async () => {
state.value = STATES.LOADING;
ipcHandlers.checkForUpdate()
.then((info) => {
updateInfo.value = info || undefined;
state.value = info
? STATES.NEW_VERSION_AVAIL
: STATES.NO_NEW_VERSION;
}).catch(() => (state.value = STATES.ERROR));
};
const downloadNewVersion = async () => {
state.value = STATES.LOADING;
ipcHandlers.downloadUpdate()
.then(() => (state.value = STATES.READY_TO_INSTALL))
.catch(() => (state.value = STATES.ERROR));
};
Comment on lines +69 to +100
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of Automata, must be a better solution for this...

const { quitAndInstall } = ipcHandlers;
const openGithubRelease = () => {
shell.openExternal(`${repository}/releases/tag/v${updateInfo?.value?.version}`);
};
const openCompare = () => {
shell.openExternal(`${repository}/compare/v${currentVersion}...v${updateInfo?.value?.version}`);
};

return {
STATES,
state,
updateInfo,
currentVersion,
checkUpdates,
downloadNewVersion,
quitAndInstall,
openGithubRelease,
openCompare
};
},
});
</script>

<style scoped>
.progress {
min-width: 10vw;
}
</style>
4 changes: 2 additions & 2 deletions src/ui/components/app/exporters/CsvExporter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import { OutputVendorName } from '@/backend/commonTypes';
import { legalPath, required } from '@/ui/components/shared/formValidations';
import { defineComponent } from '@vue/composition-api';
import { SelectDirHandler } from '@/handlers/';
import { ipcHandlers } from '@/handlers/';
import { CsvConfig } from '@/backend/configManager/configManager';
import { setupExporterConfigForm } from './exportersCommon';

Expand All @@ -42,7 +42,7 @@ export default defineComponent({
const dataToReturn = setupExporterConfigForm(OutputVendorName.CSV);

const selectFolderDialog = async () => {
const filePath = await SelectDirHandler.invoke();
const filePath = await ipcHandlers.showSaveDialog();
if (filePath) {
(dataToReturn.exporter as CsvConfig).options.filePath = filePath;
dataToReturn.changed.value = true;
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/app/exporters/JsonExporter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ import { setupExporterConfigForm } from '@/ui/components/app/exporters/exporters
import { OutputVendorName } from '@/backend/commonTypes';
import { legalPath, required } from '@/ui/components/shared/formValidations';
import { defineComponent } from '@vue/composition-api';
import { SelectDirHandler } from '@/handlers';
import { ipcHandlers } from '@/handlers/';
import { CsvConfig } from '@/backend/configManager/configManager';

export default defineComponent({
setup() {
const dataToReturn = setupExporterConfigForm(OutputVendorName.JSON);

const selectFolderDialog = async () => {
const filePath = await SelectDirHandler.invoke();
const filePath = await ipcHandlers.showSaveDialog();
if (filePath) {
(dataToReturn.exporter as CsvConfig).options.filePath = filePath;
dataToReturn.changed.value = true;
Expand Down
1 change: 1 addition & 0 deletions vue.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const defineGlobals = (config) => {
};

module.exports = {
lintOnSave: false,
pluginOptions: {
electronBuilder: {
nodeIntegration: true,
Expand Down
Loading