From 41f0682fd51b3e3203ad63f4bb145f820686b7f0 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Tue, 27 Oct 2020 10:21:55 -0700 Subject: [PATCH] Added better auto-update handling. RPS-17 --- Xdev-app-update.yml | 3 + electron/main.js | 116 +++++++++------- package.json | 2 +- src/App/App.styles.scss | 10 ++ .../price-diff-pc-formatter.styles.scss | 9 -- .../update-manager.organism.jsx | 124 ++++++++++++++++++ src/components/pages/routes/routes.page.jsx | 3 + src/ipc.types.js | 7 + src/ipc/ipc-renderer-handler.js | 15 +++ src/redux/application/application.actions.js | 4 + src/redux/application/application.reducer.js | 3 + .../application/application.selectors.js | 4 + src/redux/application/application.types.js | 2 + src/redux/user/user.sagas.js | 1 + 14 files changed, 249 insertions(+), 54 deletions(-) create mode 100644 Xdev-app-update.yml create mode 100644 src/components/organisms/update-manager/update-manager.organism.jsx diff --git a/Xdev-app-update.yml b/Xdev-app-update.yml new file mode 100644 index 0000000..79b4862 --- /dev/null +++ b/Xdev-app-update.yml @@ -0,0 +1,3 @@ + provider: s3 + bucket: rps-updater + region: ca-central-1 \ No newline at end of file diff --git a/electron/main.js b/electron/main.js index 35ee665..5184e6a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -19,6 +19,8 @@ const Nucleus = require("nucleus-nodejs"); require("./ipc-main-handler"); require("./analytics"); +autoUpdater.autoDownload = false; + autoUpdater.logger = log; autoUpdater.logger.transports.file.level = "info"; log.info("App starting...", app.getVersion()); @@ -70,10 +72,7 @@ var menu = Menu.buildFromTemplate([ { label: `Check for Updates (currently ${app.getVersion()})`, click() { - autoUpdater.checkForUpdatesAndNotify({ - title: "ImEX RPS Update Downloaded", - body: "Restart ImEX RPS to install.", - }); + checkForUpdates(); }, }, { @@ -168,7 +167,6 @@ function createWindow() { } mainWindow.maximize(); - autoUpdater.checkForUpdatesAndNotify(); globalShortcut.register("CommandOrControl+Shift+I", () => { mainWindow.webContents.toggleDevTools(); @@ -242,46 +240,22 @@ function createTray() { return appIcon; } -autoUpdater.on("checking-for-update", () => { - log.log("Checking for update..."); -}); -autoUpdater.on("update-available", (ev, info) => { - log.log("Update available."); -}); -autoUpdater.on("update-not-available", (ev, info) => { - log.log("Update not available."); -}); -autoUpdater.on("error", (ev, err) => { - log.log("Error in auto-updater."); -}); -autoUpdater.on("download-progress", (ev, progressObj) => { - log.log("Download progress..."); -}); -// autoUpdater.on("update-downloaded", (ev, info) => { -// console.log("Update downloaded; will install in 5 seconds"); +// autoUpdater.on("checking-for-update", () => { +// log.log("Checking for update..."); // }); -autoUpdater.on("update-downloaded", (ev, info) => { - Nucleus.track("UPDATE_DOWNLOADED", info); - if (process.env.NODE_ENV === "production") { - dialog.showMessageBox( - { - type: "info", - title: "Found Updates", - message: "Found updates, do you want update now?", - buttons: ["Sure", "No"], - }, - (buttonIndex) => { - if (buttonIndex === 0) { - const isSilent = true; - const isForceRunAfter = true; - autoUpdater.quitAndInstall(isSilent, isForceRunAfter); - } else { - logger.warn("Error"); - } - } - ); - } -}); +// autoUpdater.on("update-available", (ev, info) => { +// log.log("Update available."); +// }); +// autoUpdater.on("update-not-available", (ev, info) => { +// log.log("Update not available."); +// }); +// autoUpdater.on("error", (ev, err) => { +// log.log("Error in auto-updater."); +// }); + +// // autoUpdater.on("update-downloaded", (ev, info) => { +// // console.log("Update downloaded; will install in 5 seconds"); +// // }); function openNoticeWindow() { if (noticeWindow) { @@ -301,3 +275,57 @@ function openNoticeWindow() { noticeWindow = null; }); } + +ipcMain.on(ipcTypes.app.toMain.checkForUpdates, (event, args) => { + checkForUpdates(); +}); + +ipcMain.on(ipcTypes.app.toMain.downloadUpdates, (event, args) => { + autoUpdater.downloadUpdate(); +}); + +ipcMain.on(ipcTypes.app.toMain.installUpdates, (event, args) => { + const isSilent = true; + const isForceRunAfter = true; + autoUpdater.quitAndInstall(isSilent, isForceRunAfter); +}); + +autoUpdater.on("download-progress", (ev) => { + mainWindow.webContents.send(ipcTypes.app.toRenderer.downloadProgress, ev); +}); + +autoUpdater.on("update-downloaded", (ev, info) => { + Nucleus.track("UPDATE_DOWNLOADED", info); + // if (process.env.NODE_ENV === "production") { + + dialog.showMessageBox( + { + type: "info", + title: "ImeX RPS Update Manager", + message: `ImEX RPS is ready to update to Version ${ev.version}. It is highly recommended that you update immediately. Would you like to update now? RPS will automatically restart.`, + buttons: ["Yes", "No"], + }, + (buttonIndex) => { + if (buttonIndex === 0) { + const isSilent = true; + const isForceRunAfter = true; + autoUpdater.quitAndInstall(isSilent, isForceRunAfter); + } else { + logger.error("Error"); + } + } + ); +}); +async function checkForUpdates() { + try { + log.info("Checking for updates."); + const result = await autoUpdater.checkForUpdates(); + const { updateInfo } = result; + mainWindow.webContents.send( + ipcTypes.app.toRenderer.updateAvailable, + updateInfo + ); + } catch (error) { + log.error("Error while checking for update", error); + } +} diff --git a/package.json b/package.json index 91a8d53..739f6de 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "ImEX RPS", "author": "ImEX Systems Inc. ", "description": "ImEX RPS", - "version": "1.0.5", + "version": "1.0.3", "main": "electron/main.js", "homepage": "./", "dependencies": { diff --git a/src/App/App.styles.scss b/src/App/App.styles.scss index 4d1288d..dc2299e 100644 --- a/src/App/App.styles.scss +++ b/src/App/App.styles.scss @@ -78,3 +78,13 @@ body { .ant-tabs-content { height: 100%; } + +.blink_me { + animation: blinker 1s linear infinite; +} + +@keyframes blinker { + 50% { + opacity: 0; + } +} diff --git a/src/components/atoms/price-diff-pc-formatter/price-diff-pc-formatter.styles.scss b/src/components/atoms/price-diff-pc-formatter/price-diff-pc-formatter.styles.scss index bd4b9f5..e69de29 100644 --- a/src/components/atoms/price-diff-pc-formatter/price-diff-pc-formatter.styles.scss +++ b/src/components/atoms/price-diff-pc-formatter/price-diff-pc-formatter.styles.scss @@ -1,9 +0,0 @@ -.blink_me { - animation: blinker 1s linear infinite; -} - -@keyframes blinker { - 50% { - opacity: 0; - } -} diff --git a/src/components/organisms/update-manager/update-manager.organism.jsx b/src/components/organisms/update-manager/update-manager.organism.jsx new file mode 100644 index 0000000..a8c468c --- /dev/null +++ b/src/components/organisms/update-manager/update-manager.organism.jsx @@ -0,0 +1,124 @@ +import { AlertFilled } from "@ant-design/icons"; +import { Button, Layout, Progress } from "antd"; +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import ipcTypes from "../../../ipc.types"; +import { + selectUpdateAvailable, + selectUpdateProgress, +} from "../../../redux/application/application.selectors"; +const { ipcRenderer } = window; +const mapStateToProps = createStructuredSelector({ + //currentUser: selectCurrentUser + updateAvailable: selectUpdateAvailable, + updateProgress: selectUpdateProgress, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export function UpdateManagerOrganism({ updateAvailable, updateProgress }) { + if (!updateAvailable) return null; + return ( + + {updateAvailable && !updateProgress && ( +
+
+ + {`An update to ImEX RPS is available. (Version ${updateAvailable.version})`} +
+ +
+ )} + {updateAvailable && updateProgress && ( +
+ + {updateProgress.percent === 100 ? ( +
+ {`Updated downloaded.`} + +
+ ) : ( + {`Downloading update at ${formatBytes( + updateProgress.bytesPerSecond + )})`} + )} +
+ )} +
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(UpdateManagerOrganism); + +// download Info +// { +// // bytesPerSecond: 6633258; +// // delta: 2479242; +// // percent: 100; +// // total: 95651575; +// // transferred: 95651575; +// } + +function formatBytes(bytes) { + var marker = 1024; // Change to 1000 if required + var decimal = 1; // Change as required + var kiloBytes = marker; // One Kilobyte is 1024 bytes + var megaBytes = marker * marker; // One MB is 1024 KB + var gigaBytes = marker * marker * marker; // One GB is 1024 MB + //var teraBytes = marker * marker * marker * marker; // One TB is 1024 GB + + // return bytes if less than a KB + if (bytes < kiloBytes) return bytes + " Bytes/sec"; + // return KB if less than a MB + else if (bytes < megaBytes) + return (bytes / kiloBytes).toFixed(decimal) + " KB/sec"; + // return MB if less than a GB + else if (bytes < gigaBytes) + return (bytes / megaBytes).toFixed(decimal) + " MB/sec"; + // return GB if less than a TB + else return (bytes / gigaBytes).toFixed(decimal) + " GB/sec"; +} diff --git a/src/components/pages/routes/routes.page.jsx b/src/components/pages/routes/routes.page.jsx index 1e0b001..b9299bb 100644 --- a/src/components/pages/routes/routes.page.jsx +++ b/src/components/pages/routes/routes.page.jsx @@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../../redux/user/user.selectors"; import ErrorResultAtom from "../../atoms/error-result/error-result.atom"; import SiderMenuOrganism from "../../organisms/sider-menu/sider-menu.organism"; +import UpdateManagerOrganism from "../../organisms/update-manager/update-manager.organism"; import JobsPage from "../jobs/jobs.page"; import ReportingPage from "../reporting/reporting.page"; import ScanPage from "../scan/scan.page"; @@ -39,6 +40,8 @@ export function RoutesPage({ bodyshop }) { + + ); diff --git a/src/ipc.types.js b/src/ipc.types.js index 794d547..fda1b6b 100644 --- a/src/ipc.types.js +++ b/src/ipc.types.js @@ -9,6 +9,13 @@ exports.default = { setAcceptableInsCoNm: "setAcceptableInsCoNm", setUserName: "setUserName", track: "analytics_track", + checkForUpdates: "app_checkForUpdates", + downloadUpdates: "app_downloadUpdates", + installUpdates: "app_installupdates", + }, + toRenderer: { + updateAvailable: "app_updateAvailable", + downloadProgress: "app_downloadProgress", }, }, store: { diff --git a/src/ipc/ipc-renderer-handler.js b/src/ipc/ipc-renderer-handler.js index b14dbdf..84100e6 100644 --- a/src/ipc/ipc-renderer-handler.js +++ b/src/ipc/ipc-renderer-handler.js @@ -1,6 +1,8 @@ import ipcTypes from "../ipc.types"; import { setSettings, + setUpdateAvailable, + setUpdateProgress, setWatchedPaths, setWatcherStatus, } from "../redux/application/application.actions"; @@ -65,3 +67,16 @@ ipcRenderer.on( store.dispatch(setScanEstimateList(listOfEstimates)); } ); + +ipcRenderer.on( + ipcTypes.default.app.toRenderer.updateAvailable, + async (event, updateInfo) => { + store.dispatch(setUpdateAvailable(updateInfo)); + } +); +ipcRenderer.on( + ipcTypes.default.app.toRenderer.downloadProgress, + async (event, progress) => { + store.dispatch(setUpdateProgress(progress)); + } +); diff --git a/src/redux/application/application.actions.js b/src/redux/application/application.actions.js index 060e812..71c4a0c 100644 --- a/src/redux/application/application.actions.js +++ b/src/redux/application/application.actions.js @@ -48,3 +48,7 @@ export const setUpdateAvailable = (available) => ({ type: ApplicationActionTypes.SET_UPDATE_AVAILABLE, payload: available, }); +export const setUpdateProgress = (progress) => ({ + type: ApplicationActionTypes.SET_UPDATE_PROGRESS, + payload: progress, +}); diff --git a/src/redux/application/application.reducer.js b/src/redux/application/application.reducer.js index d19df1a..c4896cc 100644 --- a/src/redux/application/application.reducer.js +++ b/src/redux/application/application.reducer.js @@ -8,6 +8,7 @@ const INITIAL_STATE = { selectedJobTargetPc: 0, settings: {}, updateAvailable: false, + updateProgress: null, }; const { ipcRenderer } = window; @@ -63,6 +64,8 @@ const applicationReducer = (state = INITIAL_STATE, action) => { return { ...state, settings: { ...state.settings, ...action.payload } }; case ApplicationActionTypes.SET_UPDATE_AVAILABLE: return { ...state, updateAvailable: action.payload }; + case ApplicationActionTypes.SET_UPDATE_PROGRESS: + return { ...state, updateProgress: action.payload }; default: return state; } diff --git a/src/redux/application/application.selectors.js b/src/redux/application/application.selectors.js index f474fbd..aad8463 100644 --- a/src/redux/application/application.selectors.js +++ b/src/redux/application/application.selectors.js @@ -36,3 +36,7 @@ export const selectUpdateAvailable = createSelector( [selectApplication], (application) => application.updateAvailable ); +export const selectUpdateProgress = createSelector( + [selectApplication], + (application) => application.updateProgress +); diff --git a/src/redux/application/application.types.js b/src/redux/application/application.types.js index 25f7ddf..3e24870 100644 --- a/src/redux/application/application.types.js +++ b/src/redux/application/application.types.js @@ -9,5 +9,7 @@ const ApplicationActionTypes = { SET_SELECTED_JOB_TARGET_PC_SUCCESS: "SET_SELECTED_JOB_TARGET_PC_SUCCESS", SET_SETTINGS: "SET_SETTINGS", SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE", + SET_UPDATE_PROGRESS: "SET_UPDATE_PROGRESS", + }; export default ApplicationActionTypes; diff --git a/src/redux/user/user.sagas.js b/src/redux/user/user.sagas.js index 57fdcfa..c298c2a 100644 --- a/src/redux/user/user.sagas.js +++ b/src/redux/user/user.sagas.js @@ -128,6 +128,7 @@ export function* signInSuccessSaga({ payload }) { LogRocket.identify(payload.email, { email: payload.email, }); + ipcRenderer.send(ipcTypes.default.app.toMain.checkForUpdates); ipcRenderer.send(ipcTypes.default.app.toMain.track, { event: "SIGN_IN_SUCCESS",