Skip to content

Commit 0fbf78f

Browse files
JohnJohn
authored andcommitted
255: Error dialogs
1 parent 0f5ef64 commit 0fbf78f

File tree

22 files changed

+1925
-89
lines changed

22 files changed

+1925
-89
lines changed

electron/core/plugin-manager/execution/facade.js

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Plugin from "./Plugin";
99
import { register } from "./activation-manager";
10+
import plugins from "../../../../web/public/plugins/plugin.json"
1011

1112
/**
1213
* @typedef {Object.<string, any>} installOptions The {@link https://www.npmjs.com/package/pacote|pacote options}
@@ -65,19 +66,23 @@ export async function getActive() {
6566
return;
6667
}
6768
// eslint-disable-next-line no-undef
68-
const plgList = await window.pluggableElectronIpc.getActive();
69-
return plgList.map(
70-
(plugin) =>
71-
new Plugin(
72-
plugin.name,
73-
plugin.url,
74-
plugin.activationPoints,
75-
plugin.active,
76-
plugin.description,
77-
plugin.version,
78-
plugin.icon
79-
)
80-
);
69+
if(window.pluggableElectronIpc){
70+
// eslint-disable-next-line no-undef
71+
const plgList = await window.pluggableElectronIpc.getActive();
72+
return plgList.map(
73+
(plugin) =>
74+
new Plugin(
75+
plugin.name,
76+
plugin.url,
77+
plugin.activationPoints,
78+
plugin.active,
79+
plugin.description,
80+
plugin.version,
81+
plugin.icon
82+
)
83+
);
84+
}
85+
return Promise.resolve(plugins);
8186
}
8287

8388
/**
@@ -89,18 +94,18 @@ export async function registerActive() {
8994
if (typeof window === "undefined") {
9095
return;
9196
}
92-
// eslint-disable-next-line no-undef
93-
const plgList = await window.pluggableElectronIpc.getActive();
94-
plgList.forEach((plugin) =>
95-
register(
96-
new Plugin(
97-
plugin.name,
98-
plugin.url,
99-
plugin.activationPoints,
100-
plugin.active
97+
// eslint-disable-next-line no-undef
98+
const plgList = await getActive()
99+
plgList.forEach((plugin) =>
100+
register(
101+
new Plugin(
102+
plugin.name,
103+
plugin.url,
104+
plugin.activationPoints,
105+
plugin.active
106+
)
101107
)
102-
)
103-
);
108+
);
104109
}
105110

106111
/**

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
"workspaces": {
55
"packages": [
66
"electron",
7-
"web"
7+
"web",
8+
"server"
89
],
910
"nohoist": [
1011
"electron",
1112
"electron/**",
1213
"web",
13-
"web/**"
14+
"web/**",
15+
"server",
16+
"server/**"
1417
]
1518
},
1619
"scripts": {
@@ -31,7 +34,10 @@
3134
"build:publish": "yarn build:web && yarn workspace jan build:publish",
3235
"build:publish-darwin": "yarn build:web && yarn workspace jan build:publish-darwin",
3336
"build:publish-win32": "yarn build:web && yarn workspace jan build:publish-win32",
34-
"build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux"
37+
"build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux",
38+
"buid:web-plugins": "yarn build:plugins && yarn build:web && cp \"./plugins/data-plugin/dist/esm/index.js\" \"./web/public/plugins/data-plugin\" && cp \"./plugins/inference-plugin/dist/index.js\" \"./web/public/plugins/inference-plugin\" && cp \"./plugins/model-management-plugin/dist/index.js\" \"./web/public/plugins/model-management-plugin\" && cp \"./plugins/monitoring-plugin/dist/index.js\" \"./web/public/plugins/monitoring-plugin\"",
39+
"server:dev": "yarn buid:web-plugins && cpx \"web/out/**\" \"server/renderer/\" && mkdir -p ./server/@janhq && cp -r ./plugins/* ./server/@janhq && yarn workspace server dev"
40+
3541
},
3642
"devDependencies": {
3743
"concurrently": "^8.2.1",

plugins/data-plugin/module.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const dbs: Record<string, any> = {};
1616
*/
1717
function createCollection(name: string, schema?: { [key: string]: any }): Promise<void> {
1818
return new Promise<void>((resolve) => {
19-
const dbPath = path.join(app.getPath("userData"), "databases");
19+
const dbPath = path.join(appPath(), "databases");
2020
if (!fs.existsSync(dbPath)) fs.mkdirSync(dbPath);
2121
const db = new PouchDB(`${path.join(dbPath, name)}`);
2222
dbs[name] = db;
@@ -226,6 +226,13 @@ function findMany(
226226
.then((data) => data.docs); // Return documents
227227
}
228228

229+
function appPath() {
230+
if (app) {
231+
return app.getPath("userData");
232+
}
233+
return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share");
234+
}
235+
229236
module.exports = {
230237
createCollection,
231238
deleteCollection,

plugins/inference-plugin/module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ function killSubprocess() {
109109
}
110110
}
111111

112+
function appPath() {
113+
if (app) {
114+
return app.getPath("userData");
115+
}
116+
return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share");
117+
}
118+
112119
module.exports = {
113120
initModel,
114121
killSubprocess,

plugins/model-management-plugin/index.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,54 @@ import {
55
downloadFile,
66
deleteFile,
77
store,
8+
EventName,
9+
events
810
} from "@janhq/core";
911
import { parseToModel } from "./helper";
1012

11-
const downloadModel = (product) => downloadFile(product.downloadUrl, product.fileName);
13+
const downloadModel = (product) => {
14+
downloadFile(product.downloadUrl, product.fileName);
15+
checkDownloadProgress(product.fileName);
16+
}
17+
18+
async function checkDownloadProgress(fileName: string) {
19+
if (typeof window !== "undefined" && typeof (window as any).electronAPI === "undefined") {
20+
const intervalId = setInterval(() => {
21+
fetchDownloadProgress(fileName, intervalId);
22+
}, 3000);
23+
}
24+
}
25+
26+
async function fetchDownloadProgress(fileName: string, intervalId: NodeJS.Timeout): Promise<any> {
27+
const response = await fetch("/api/v1/downloadProgress", {
28+
method: 'POST',
29+
body: JSON.stringify({ fileName: fileName }),
30+
headers: { 'Content-Type': 'application/json', 'Authorization': '' }
31+
});
32+
33+
if (!response.ok) {
34+
events.emit(EventName.OnDownloadError, null);
35+
clearInterval(intervalId);
36+
return;
37+
}
38+
const json = await response.json();
39+
if (isEmptyObject(json)) {
40+
if (!fileName) {
41+
clearInterval(intervalId);
42+
}
43+
return;
44+
}
45+
if (json.success === true) {
46+
events.emit(EventName.OnDownloadSuccess, json);
47+
clearInterval(intervalId);
48+
} else {
49+
events.emit(EventName.OnDownloadUpdate, json);
50+
}
51+
}
52+
53+
function isEmptyObject(ojb: any): boolean {
54+
return Object.keys(ojb).length === 0;
55+
}
1256

1357
const deleteModel = (path) => deleteFile(path);
1458

@@ -87,6 +131,7 @@ function getModelById(modelId: string): Promise<any> {
87131

88132
function onStart() {
89133
store.createCollection("models", {});
134+
checkDownloadProgress(null);
90135
}
91136

92137
// Register all the above functions and objects with the relevant extension points

server/main.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import express, { Express, Request, Response, NextFunction } from 'express'
2+
import cors from "cors";
3+
import { resolve } from "path";
4+
import { unlink, createWriteStream } from "fs";
5+
const progress = require("request-progress");
6+
const path = require("path");
7+
const request = require("request");
8+
9+
interface ProgressState {
10+
percent: number;
11+
speed: number;
12+
size: {
13+
total: number;
14+
transferred: number;
15+
};
16+
time: {
17+
elapsed: number;
18+
remaining: number;
19+
};
20+
success?: boolean | undefined;
21+
fileName: string;
22+
}
23+
24+
const options: cors.CorsOptions = { origin: "*" };
25+
const requiredModules: Record<string, any> = {};
26+
const port = process.env.PORT || 4000;
27+
const dataDir = __dirname;
28+
type DownloadProgress = Record<string, ProgressState>;
29+
const downloadProgress: DownloadProgress = {};
30+
const app: Express = express()
31+
app.use(express.static(dataDir + '/renderer'))
32+
app.use(cors(options))
33+
app.use(express.json());
34+
35+
/**
36+
* Execute a plugin module function via API call
37+
*
38+
* @param modulePath path to module name to import
39+
* @param method function name to execute. The methods "deleteFile" and "downloadFile" will call the server function {@link deleteFile}, {@link downloadFile} instead of the plugin function.
40+
* @param args arguments to pass to the function
41+
* @returns Promise<any>
42+
*
43+
*/
44+
app.post('/api/v1/invokeFunction', (req: Request, res: Response, next: NextFunction): void => {
45+
const method = req.body["method"];
46+
const args = req.body["args"];
47+
switch (method) {
48+
case "deleteFile":
49+
deleteFile(args).then(() => res.json(Object())).catch((err: any) => next(err));
50+
break;
51+
case "downloadFile":
52+
downloadFile(args.downloadUrl, args.fileName).then(() => res.json(Object())).catch((err: any) => next(err));
53+
break;
54+
default:
55+
const result = invokeFunction(req.body["modulePath"], method, args)
56+
if (typeof result === "undefined") {
57+
res.json(Object())
58+
} else {
59+
result?.then((result: any) => {
60+
res.json(result)
61+
}).catch((err: any) => next(err));
62+
}
63+
}
64+
});
65+
66+
app.post('/api/v1/downloadProgress', (req: Request, res: Response): void => {
67+
const fileName = req.body["fileName"];
68+
if (fileName && downloadProgress[fileName]) {
69+
res.json(downloadProgress[fileName])
70+
return;
71+
} else {
72+
const obj = downloadingFile();
73+
if (obj) {
74+
res.json(obj)
75+
return;
76+
}
77+
}
78+
res.json(Object());
79+
});
80+
81+
app.use((err: Error, req: Request, res: Response, next: NextFunction): void => {
82+
res.status(500);
83+
res.json({ error: err?.message ?? "Internal Server Error" })
84+
});
85+
86+
app.listen(port, () => console.log(`Application is running on port ${port}`));
87+
88+
89+
async function invokeFunction(modulePath: string, method: string, args: any): Promise<any> {
90+
console.log(modulePath, method, args);
91+
const module = require(/* webpackIgnore: true */ path.join(
92+
dataDir,
93+
"",
94+
modulePath
95+
));
96+
requiredModules[modulePath] = module;
97+
if (typeof module[method] === "function") {
98+
return module[method](...args);
99+
} else {
100+
return Promise.resolve();
101+
}
102+
}
103+
104+
function downloadModel(downloadUrl: string, fileName: string): void {
105+
const userDataPath = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")
106+
107+
const destination = resolve(userDataPath, fileName);
108+
console.log("Download file", fileName, "to", destination);
109+
progress(request(downloadUrl), {})
110+
.on("progress", function (state: any) {
111+
downloadProgress[fileName] = {
112+
...state,
113+
fileName,
114+
success: undefined
115+
};
116+
console.log("downloading file", fileName, (state.percent * 100).toFixed(2) + '%');
117+
})
118+
.on("error", function (err: Error) {
119+
downloadProgress[fileName] = {
120+
...downloadProgress[fileName],
121+
success: false,
122+
fileName: fileName,
123+
};
124+
})
125+
.on("end", function () {
126+
downloadProgress[fileName] = {
127+
...downloadProgress[fileName],
128+
success: true,
129+
fileName: fileName,
130+
};
131+
})
132+
.pipe(createWriteStream(destination));
133+
}
134+
135+
function deleteFile(filePath: string): Promise<void> {
136+
const userDataPath = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")
137+
const fullPath = resolve(userDataPath, filePath);
138+
return new Promise((resolve, reject) => {
139+
unlink(fullPath, function (err) {
140+
if (err && err.code === "ENOENT") {
141+
reject(Error(`File does not exist: ${err}`));
142+
} else if (err) {
143+
reject(Error(`File delete error: ${err}`));
144+
} else {
145+
console.log(`Delete file ${filePath} from ${fullPath}`)
146+
resolve();
147+
}
148+
});
149+
})
150+
}
151+
152+
function downloadingFile(): ProgressState | undefined {
153+
const obj = Object.values(downloadProgress).find(obj => obj && typeof obj.success === "undefined")
154+
return obj
155+
}
156+
157+
158+
async function downloadFile(downloadUrl: string, fileName: string): Promise<void> {
159+
return new Promise((resolve, reject) => {
160+
const obj = downloadingFile();
161+
if (obj) {
162+
reject(Error(obj.fileName + " is being downloaded!"))
163+
return;
164+
};
165+
(async () => {
166+
downloadModel(downloadUrl, fileName);
167+
})().catch(e => {
168+
console.error("downloadModel", fileName, e);
169+
});
170+
resolve();
171+
});
172+
}

server/nodemon.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"watch": [
3+
"main.ts"
4+
]
5+
}

0 commit comments

Comments
 (0)