diff --git a/gui-js/apps/minsky-electron/src/app/app.ts b/gui-js/apps/minsky-electron/src/app/app.ts index 828a44d50..e8c11e6f7 100644 --- a/gui-js/apps/minsky-electron/src/app/app.ts +++ b/gui-js/apps/minsky-electron/src/app/app.ts @@ -46,6 +46,21 @@ export default class App { backend('minsky.pushFlags'); await HelpFilesManager.initialize(helpFilesFolder); App.initMainWindow(); + let ravelPlugin=StoreManager.store.get('ravelPlugin'); + if (ravelPlugin) { + // if this is set (after a full reinstall of Minsky, kick off updating the Ravel plugin) + App.mainWindow.webContents.downloadURL(ravelPlugin); + App.mainWindow.webContents.session.on('will-download',CommandsManager.downloadRavel); + StoreManager.store.set('ravelPlugin',''); + } + // check if ravel is approaching its expiry date, and nag user to upgrade if so + let daysLeft=await minsky.daysUntilRavelExpires(); + if (await minsky.ravelVersion()!=="unavailable" && daysLeft<30) { + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: `The Ravel plugin is expiring in ${daysLeft} days,\nplease update the plugin using the File>Upgrade menu`, + type: 'info', + }); + } await App.initMenu(); App.loadMainWindow(); backend('minsky.popFlags'); diff --git a/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts index a65b467e6..c10d621dd 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/ApplicationMenuManager.ts @@ -81,7 +81,7 @@ export class ApplicationMenuManager { label: 'File', submenu: [ { - label: 'About Minsky', + label: 'About', click() { setInterval(()=> { console.log(process.memoryUsage()) @@ -98,9 +98,7 @@ export class ApplicationMenuManager { }, { label: 'Upgrade', - click() { - shell.openExternal('https://www.patreon.com/hpcoder'); - }, + click() {CommandsManager.upgrade();}, }, { label: 'New System', diff --git a/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts b/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts index 572100ab4..c6e445ceb 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/CommandsManager.ts @@ -9,14 +9,17 @@ import { importCSVvariableName, minsky, GodleyIcon, Group, IntOp, Item, Lock, Ravel, VariableBase, Wire, Utility } from '@minsky/shared'; -import { dialog, ipcMain, Menu, MenuItem, SaveDialogOptions } from 'electron'; +import { app, dialog, ipcMain, Menu, MenuItem, SaveDialogOptions,} from 'electron'; import { existsSync, unlinkSync } from 'fs'; import JSON5 from 'json5'; -import { join } from 'path'; +import { join, dirname } from 'path'; +import { tmpdir } from 'os'; import { HelpFilesManager } from './HelpFilesManager'; import { WindowManager } from './WindowManager'; import { StoreManager } from './StoreManager'; import { RecentFilesManager } from './RecentFilesManager'; +import ProgressBar from 'electron-progressbar'; +import {spawn} from 'child_process'; export class CommandsManager { static activeGodleyWindowItems = new Map(); @@ -1111,5 +1114,145 @@ export class CommandsManager { minsky.numBackups(numBackups); RecentFilesManager.updateNumberOfRecentFilesToDisplay(); } + + // handler for downloading Ravel and installing it + static downloadRavel(event,item,webContents) { + switch (process.platform) { + case 'win32': + item.setSavePath(dirname(process.execPath)+'/libravel.dll'); + break; + default: + // nothing to do - TODO implement handlers for MacOS and Linux + } + // handler for when download completed + item.once('done', (event,state)=>{ + if (state==='completed') { + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: 'Ravel plugin updated successfully - restart Ravel to use', + type: 'info', + }); + app.relaunch(); + app.quit(); + } else { + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: `Ravel plugin update failed: ${state}`, + type: 'error', + }); + webContents.close(); + } + }); + } + + // handler for downloading Minsky + static downloadMinsky(event,item,webContents) { + item.setSavePath(join(tmpdir(),item.getFilename())); + + let progress=new ProgressBar({text:"Downloading Ravel application",value: 0, indeterminate:false, closeOnComplete: true,}); + + // handler for when download completed + item.once('done', (event,state)=>{ + if (state==='completed') { + switch (process.platform) { + case 'win32': + spawn(item.getSavePath(),{detached: true, stdio: 'ignore'}); + app.quit(); + break; + default: + webContents.close(); + break; + } + } else { + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: `Ravel update failed: ${state}`, + type: 'error', + }); + webContents.close(); + } + }); + + // handler for updating progress bar + item.on('updated',(event,state)=>{ + switch (state) { + case 'progressing': + if (!progress?.isCompleted()) + progress.value=100*item.getReceivedBytes()/item.getTotalBytes(); + break; + case 'interrupted': + //item.resume(); + break; + } + }); + } + + static upgrade() { + let window=WindowManager.createWindow({ + width: 500, + height: 700, + title: '', + modal: false, + }); + + // handler for when user has logged in to initiate upgrades. + window.webContents.on('did-navigate',async ()=>{ + const urlString=window.webContents.getURL(); + if (urlString=='https://www.patreon.com/') + // user has logged out + window.close() + }); + window.webContents.on('did-redirect-navigation', async (event)=>{ + const installables=await window.webContents.executeJavaScript('document.getElementById("installables")?.innerText'); + if (installables) { + let params=new URLSearchParams(installables); + let minskyFile=params.get('minsky-asset'); + let ravelFile=params.get('ravel-asset'); + if (minskyFile) { + let minskyVersionRE=/(\d+)\.(\d+)\.(\d+)([.-])/; + let [all,major,minor,patch]=minskyVersionRE.exec(minskyFile); + let [currAll,currMajor,currMinor,currPatch,terminator]=minskyVersionRE.exec(await minsky.minskyVersion()); + if (major>currMajor || major===currMajor && + (minor>currMinor || minor===currMinor && patch>currPatch) || + terminator==='-' && // currently a beta release, so install if release nos match (since betas precede releases) + major===currMajor && minor===currMinor && patch==currPatch + ) { + if (ravelFile) { // stash ravel upgrade to be installed on next startup + StoreManager.store.set('ravelPlugin',ravelFile); + } + window.webContents.session.on('will-download',this.downloadMinsky); + window.webContents.downloadURL(minskyFile); + } else if (ravelFile) { + // currently on latest, so reinstall ravel + window.webContents.session.on('will-download',this.downloadRavel); + window.webContents.downloadURL(ravelFile); + } + else { + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: "Everything's up to date, nothing to do", + type: 'info', + }); + window.close(); + } + } + } + }); + + let clientId='abf9j0FWQTj-etln2BbRlUhJnjv11kaL9lH1nprj23NLSq3l6ELxUGkLJKIfWsKt'; + // need to pass what platform we are + switch (process.platform) { + case 'win32': var system='windows'; break; + //case 'darwin': var system='macos'; break; + //case 'linux': var system='linux'; break; + // TODO consult /etc/os-release to figure out which distro + default: + dialog.showMessageBoxSync(WindowManager.getMainWindow(),{ + message: `In app update is not available for your operating system yet, please check back later`, + type: 'error', + }); + window.close(); + return; + break; + } + // load patreon's login page + window.loadURL(`https://www.patreon.com/oauth2/authorize?response_type=code&client_id=${clientId}&redirect_uri=https://ravelation.hpcoders.com.au/ravel-downloader.cgi&state=${system}`); + } } diff --git a/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts b/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts index 124e1f26f..652d1cfac 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/StoreManager.ts @@ -16,6 +16,7 @@ interface MinskyStore { recentFiles: Array; backgroundColor: string; preferences: MinskyPreferences; + ravelPlugin: string; // used for post installation installation of Ravel } class StoreManager { @@ -34,6 +35,7 @@ class StoreManager { // focusFollowsMouse: false, numBackups: 1, }, + ravelPlugin: '', }, }); } diff --git a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts index 6e7782052..cf82709a5 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/WindowManager.ts @@ -172,7 +172,7 @@ export class WindowManager { return window; } - private static createWindow( + static createWindow( payload: CreateWindowPayload, onCloseCallback?: (ev : Electron.Event) => void ) { diff --git a/gui-js/libs/shared/src/lib/backend/minsky.ts b/gui-js/libs/shared/src/lib/backend/minsky.ts index 4f292d6d2..b04a612e9 100644 --- a/gui-js/libs/shared/src/lib/backend/minsky.ts +++ b/gui-js/libs/shared/src/lib/backend/minsky.ts @@ -1351,6 +1351,7 @@ export class Minsky extends CppClass { async copy(): Promise {return this.$callMethod('copy');} async cut(): Promise {return this.$callMethod('cut');} async cycleCheck(): Promise {return this.$callMethod('cycleCheck');} + async daysUntilRavelExpires(): Promise {return this.$callMethod('daysUntilRavelExpires');} async defaultFont(...args: any[]): Promise {return this.$callMethod('defaultFont',...args);} async definingVar(a1: string): Promise {return this.$callMethod('definingVar',a1);} async deleteAllUnits(): Promise {return this.$callMethod('deleteAllUnits');} diff --git a/gui-js/libs/shared/src/lib/constants/version.ts b/gui-js/libs/shared/src/lib/constants/version.ts index d81a3b484..0e4e9d095 100644 --- a/gui-js/libs/shared/src/lib/constants/version.ts +++ b/gui-js/libs/shared/src/lib/constants/version.ts @@ -1 +1 @@ -export const version="3.4.0-alpha.14"; +export const version="3.4.0-alpha.15"; diff --git a/gui-js/package-lock.json b/gui-js/package-lock.json index 596e10b00..4d2dac417 100644 --- a/gui-js/package-lock.json +++ b/gui-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "minsky", - "version": "3.4.0-alpha.14", + "version": "3.4.0-alpha.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "minsky", - "version": "3.4.0-alpha.14", + "version": "3.4.0-alpha.15", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/gui-js/package.json b/gui-js/package.json index 350ceb951..50897e3f5 100644 --- a/gui-js/package.json +++ b/gui-js/package.json @@ -1,6 +1,6 @@ { "name": "minsky", - "version":"3.4.0-alpha.14", + "version":"3.4.0-alpha.15", "author": "High Performance Coders", "description": "Graphical dynamical systems simulator oriented towards economics", "build": { diff --git a/minskyVersion.h b/minskyVersion.h index 2147c68d9..dbe65c872 100644 --- a/minskyVersion.h +++ b/minskyVersion.h @@ -1 +1 @@ -#define MINSKY_VERSION "3.4.0-alpha.14" +#define MINSKY_VERSION "3.4.0-alpha.15" diff --git a/model/minsky.h b/model/minsky.h index 680813fa0..16e68a998 100644 --- a/model/minsky.h +++ b/model/minsky.h @@ -335,7 +335,8 @@ namespace minsky else return "unavailable"; } static bool ravelExpired() {return ravel::Ravel::available() && ravel::Ravel::daysUntilExpired()<0;} - + static int daysUntilRavelExpires() {return ravel::Ravel::daysUntilExpired();} + std::string fileVersion; ///< Minsky version file was saved under unsigned maxHistory{100}; ///< maximum no. of history states to save