mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-01-11 20:10:20 +00:00
Add GUI States
This adds states for the GUI that get saved to a file, notably this means that queue's of services are no longer lost when changing services, or closing and reopening the program. Also changed was a few spelling fixes
This commit is contained in:
parent
f7ddaf1176
commit
c3dab33f6b
12 changed files with 93 additions and 53 deletions
15
@types/messageHandler.d.ts
vendored
15
@types/messageHandler.d.ts
vendored
|
|
@ -4,6 +4,7 @@ import type { AvailableMuxer } from '../modules/module.args';
|
|||
import { LanguageItem } from '../modules/module.langsData';
|
||||
|
||||
export interface MessageHandler {
|
||||
name: string
|
||||
auth: (data: AuthData) => Promise<AuthResponse>;
|
||||
checkToken: () => Promise<CheckTokenResponse>;
|
||||
search: (data: SearchData) => Promise<SearchResponse>,
|
||||
|
|
@ -21,8 +22,7 @@ export interface MessageHandler {
|
|||
removeFromQueue: (index: number) => void,
|
||||
clearQueue: () => void,
|
||||
setDownloadQueue: (data: boolean) => void,
|
||||
getDownloadQueue: () => Promise<boolean>,
|
||||
name: string
|
||||
getDownloadQueue: () => Promise<boolean>
|
||||
}
|
||||
|
||||
export type FolderTypes = 'content' | 'config';
|
||||
|
|
@ -131,7 +131,7 @@ export type ProgressData = {
|
|||
bytes: number
|
||||
};
|
||||
|
||||
export type PossibleMessanges = keyof ServiceHandler;
|
||||
export type PossibleMessages = keyof ServiceHandler;
|
||||
|
||||
export type DownloadInfo = {
|
||||
image: string,
|
||||
|
|
@ -146,4 +146,13 @@ export type DownloadInfo = {
|
|||
export type ExtendedProgress = {
|
||||
progress: ProgressData,
|
||||
downloadInfo: DownloadInfo
|
||||
}
|
||||
|
||||
export type GuiState = {
|
||||
setup: Boolean,
|
||||
services: Record<string, GuiStateService>
|
||||
}
|
||||
|
||||
export type GuiStateService = {
|
||||
queue: QueueItem[]
|
||||
}
|
||||
2
@types/ws.d.ts
vendored
2
@types/ws.d.ts
vendored
|
|
@ -33,7 +33,7 @@ export type MessageTypes = {
|
|||
'setup': ['funi'|'crunchy'|'hidive'|undefined, undefined],
|
||||
'openFile': [[FolderTypes, string], undefined],
|
||||
'openURL': [string, undefined],
|
||||
'setuped': [undefined, boolean],
|
||||
'isSetup': [undefined, boolean],
|
||||
'setupServer': [GUIConfig, boolean],
|
||||
'requirePassword': [undefined, boolean],
|
||||
'getQueue': [undefined, QueueItem[]],
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"setuped": true
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import { useSnackbar } from 'notistack';
|
|||
import { LockOutlined, PowerSettingsNew } from '@mui/icons-material';
|
||||
import { GUIConfig } from '../../../../modules/module.cfg-loader';
|
||||
|
||||
export type FrontEndMessanges = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise<boolean> });
|
||||
export type FrontEndMessages = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise<boolean> });
|
||||
|
||||
export class RandomEventHandler {
|
||||
private handler: {
|
||||
|
|
@ -38,7 +38,7 @@ export class RandomEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
export const messageChannelContext = React.createContext<FrontEndMessanges|undefined>(undefined);
|
||||
export const messageChannelContext = React.createContext<FrontEndMessages|undefined>(undefined);
|
||||
|
||||
async function messageAndResponse<T extends keyof MessageTypes>(socket: WebSocket, msg: WSMessage<T>): Promise<WSMessage<T, 1>> {
|
||||
const id = v4();
|
||||
|
|
@ -65,7 +65,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
|||
const [socket, setSocket] = React.useState<undefined|WebSocket>();
|
||||
const [publicWS, setPublicWS] = React.useState<undefined|WebSocket>();
|
||||
const [usePassword, setUsePassword] = React.useState<'waiting'|'yes'|'no'>('waiting');
|
||||
const [isSetuped, setIsSetuped] = React.useState<'waiting'|'yes'|'no'>('waiting');
|
||||
const [isSetup, setIsSetup] = React.useState<'waiting'|'yes'|'no'>('waiting');
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
|||
if (!publicWS)
|
||||
return;
|
||||
setUsePassword((await messageAndResponse(publicWS, { name: 'requirePassword', data: undefined })).data ? 'yes' : 'no');
|
||||
setIsSetuped((await messageAndResponse(publicWS, { name: 'setuped', data: undefined })).data ? 'yes' : 'no');
|
||||
setIsSetup((await messageAndResponse(publicWS, { name: 'isSetup', data: undefined })).data ? 'yes' : 'no');
|
||||
})();
|
||||
}, [publicWS]);
|
||||
|
||||
|
|
@ -190,7 +190,7 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
|||
</Box>;
|
||||
}
|
||||
|
||||
if (isSetuped === 'no') {
|
||||
if (isSetup === 'no') {
|
||||
return <Box sx={{ mt: 3, display: 'flex', flexDirection: 'column', justifyItems: 'center', alignItems: 'center' }}>
|
||||
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
|
||||
<PowerSettingsNew />
|
||||
|
|
@ -211,7 +211,8 @@ const MessageChannelProvider: FCWithChildren = ({ children }) => {
|
|||
</Box>;
|
||||
}
|
||||
|
||||
const messageHandler: FrontEndMessanges = {
|
||||
const messageHandler: FrontEndMessages = {
|
||||
name: "default",
|
||||
auth: async (data) => (await messageAndResponse(socket, { name: 'auth', data })).data,
|
||||
checkToken: async () => (await messageAndResponse(socket, { name: 'checkToken', data: undefined })).data,
|
||||
search: async (data) => (await messageAndResponse(socket, { name: 'search', data })).data,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { ServerResponse } from 'http';
|
||||
import { Server } from 'http';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { MessageHandler } from '../../@types/messageHandler';
|
||||
import { setSetuped, writeYamlCfgFile } from '../../modules/module.cfg-loader';
|
||||
import { MessageHandler, GuiState } from '../../@types/messageHandler';
|
||||
import { setState, getState, writeYamlCfgFile } from '../../modules/module.cfg-loader';
|
||||
import CrunchyHandler from './services/crunchyroll';
|
||||
import FunimationHandler from './services/funimation';
|
||||
import HidiveHandler from './services/hidive';
|
||||
|
|
@ -12,16 +12,19 @@ export default class ServiceHandler {
|
|||
|
||||
private service: MessageHandler|undefined = undefined;
|
||||
private ws: WebSocketHandler;
|
||||
private state: GuiState;
|
||||
|
||||
constructor(server: Server<typeof IncomingMessage, typeof ServerResponse>) {
|
||||
this.ws = new WebSocketHandler(server);
|
||||
this.handleMessanges();
|
||||
this.handleMessages();
|
||||
this.state = getState();
|
||||
}
|
||||
|
||||
private handleMessanges() {
|
||||
private handleMessages() {
|
||||
this.ws.events.on('setupServer', ({ data }, respond) => {
|
||||
writeYamlCfgFile('gui', data);
|
||||
setSetuped(true);
|
||||
this.state.setup = true;
|
||||
setState(this.state);
|
||||
respond(true);
|
||||
process.exit(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,35 @@
|
|||
import { DownloadInfo, FolderTypes, ProgressData, QueueItem } from '../../../@types/messageHandler';
|
||||
import { DownloadInfo, FolderTypes, GuiState, ProgressData, QueueItem } from '../../../@types/messageHandler';
|
||||
import { RandomEvent, RandomEvents } from '../../../@types/randomEvents';
|
||||
import WebSocketHandler from '../websocket';
|
||||
import open from 'open';
|
||||
import { cfg } from '..';
|
||||
import path from 'path';
|
||||
import { console } from '../../../modules/log';
|
||||
import { getState, setState } from '../../../modules/module.cfg-loader';
|
||||
|
||||
export default class Base {
|
||||
|
||||
constructor(private ws: WebSocketHandler) {}
|
||||
private state: GuiState;
|
||||
public name = 'default';
|
||||
constructor(private ws: WebSocketHandler) {
|
||||
this.state = getState();
|
||||
}
|
||||
|
||||
private downloading = false;
|
||||
|
||||
private queue: QueueItem[] = [];
|
||||
private workOnQueue = false;
|
||||
|
||||
initState() {
|
||||
if (this.state.services[this.name]) {
|
||||
this.queue = this.state.services[this.name].queue;
|
||||
this.queueChange();
|
||||
} else {
|
||||
this.state.services[this.name] = {
|
||||
'queue': []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDownloading(downloading: boolean) {
|
||||
this.downloading = downloading;
|
||||
}
|
||||
|
|
@ -109,6 +124,8 @@ export default class Base {
|
|||
this.queue = this.queue.slice(1);
|
||||
this.queueChange();
|
||||
}
|
||||
this.state.services[this.name].queue = this.queue;
|
||||
setState(this.state);
|
||||
}
|
||||
|
||||
public async onFinish() {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
super(ws);
|
||||
this.crunchy = new Crunchy();
|
||||
this.crunchy.refreshToken();
|
||||
this.initState();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string): Promise<EpisodeListResponse> {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class FunimationHandler extends Base implements MessageHandler {
|
|||
constructor(ws: WebSocketHandler) {
|
||||
super(ws);
|
||||
this.funi = new Funimation();
|
||||
this.initState();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string) : Promise<EpisodeListResponse> {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class HidiveHandler extends Base implements MessageHandler {
|
|||
super(ws);
|
||||
this.hidive = new Hidive();
|
||||
this.hidive.doInit();
|
||||
this.initState();
|
||||
}
|
||||
|
||||
public auth(data: AuthData) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { RandomEvent, RandomEvents } from '../../@types/randomEvents';
|
|||
import { MessageTypes, UnknownWSMessage, WSMessage } from '../../@types/ws';
|
||||
import { EventEmitter } from 'events';
|
||||
import { cfg } from '.';
|
||||
import { isSetuped } from '../../modules/module.cfg-loader';
|
||||
import { getState } from '../../modules/module.cfg-loader';
|
||||
import { console } from '../../modules/log';
|
||||
|
||||
declare interface ExternalEvent {
|
||||
|
|
@ -81,6 +81,7 @@ export default class WebSocketHandler {
|
|||
export class PublicWebSocket {
|
||||
private wsServer: ws.Server;
|
||||
|
||||
private state = getState();
|
||||
constructor(server: Server) {
|
||||
this.wsServer = new ws.WebSocketServer({ noServer: true, path: '/public' });
|
||||
|
||||
|
|
@ -90,8 +91,8 @@ export class PublicWebSocket {
|
|||
socket.on('message', (msg) => {
|
||||
const data = JSON.parse(msg.toString()) as UnknownWSMessage;
|
||||
switch (data.name) {
|
||||
case 'setuped':
|
||||
this.send(socket, data.id, data.name, isSetuped());
|
||||
case 'isSetup':
|
||||
this.send(socket, data.id, data.name, this.state.setup);
|
||||
break;
|
||||
case 'requirePassword':
|
||||
this.send(socket, data.id, data.name, cfg.gui.password !== undefined);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import yaml from 'yaml';
|
|||
import fs from 'fs-extra';
|
||||
import { lookpath } from 'lookpath';
|
||||
import { console } from './log';
|
||||
import { GuiState } from '../@types/messageHandler';
|
||||
|
||||
// new-cfg
|
||||
const workingDir = (process as NodeJS.Process & {
|
||||
|
|
@ -15,17 +16,17 @@ const binCfgFile = path.join(workingDir, 'config', 'bin-path');
|
|||
const dirCfgFile = path.join(workingDir, 'config', 'dir-path');
|
||||
const guiCfgFile = path.join(workingDir, 'config', 'gui');
|
||||
const cliCfgFile = path.join(workingDir, 'config', 'cli-defaults');
|
||||
const hdProfileCfgFile = path.join(workingDir, 'config', 'hd_profile');
|
||||
const sessCfgFile = {
|
||||
const hdPflCfgFile = path.join(workingDir, 'config', 'hd_profile');
|
||||
const sessCfgFile = {
|
||||
funi: path.join(workingDir, 'config', 'funi_sess'),
|
||||
cr: path.join(workingDir, 'config', 'cr_sess'),
|
||||
hd: path.join(workingDir, 'config', 'hd_sess')
|
||||
cr: path.join(workingDir, 'config', 'cr_sess'),
|
||||
hd: path.join(workingDir, 'config', 'hd_sess')
|
||||
};
|
||||
const setupFile = path.join(workingDir, 'config', 'setup');
|
||||
const stateFile = path.join(workingDir, 'config', 'guistate');
|
||||
const tokenFile = {
|
||||
funi: path.join(workingDir, 'config', 'funi_token'),
|
||||
cr: path.join(workingDir, 'config', 'cr_token'),
|
||||
hd: path.join(workingDir, 'config', 'hd_token')
|
||||
cr: path.join(workingDir, 'config', 'cr_token'),
|
||||
hd: path.join(workingDir, 'config', 'hd_token')
|
||||
};
|
||||
|
||||
export const ensureConfig = () => {
|
||||
|
|
@ -256,10 +257,10 @@ const saveHDToken = (data: Record<string, unknown>) => {
|
|||
};
|
||||
|
||||
const saveHDProfile = (data: Record<string, unknown>) => {
|
||||
const cfgFolder = path.dirname(hdProfileCfgFile);
|
||||
const cfgFolder = path.dirname(hdPflCfgFile);
|
||||
try{
|
||||
fs.ensureDirSync(cfgFolder);
|
||||
fs.writeFileSync(`${hdProfileCfgFile}.yml`, yaml.stringify(data));
|
||||
fs.writeFileSync(`${hdPflCfgFile}.yml`, yaml.stringify(data));
|
||||
}
|
||||
catch(e){
|
||||
console.error('Can\'t save profile file to disk!');
|
||||
|
|
@ -267,7 +268,7 @@ const saveHDProfile = (data: Record<string, unknown>) => {
|
|||
};
|
||||
|
||||
const loadHDProfile = () => {
|
||||
let profile = loadYamlCfgFile(hdProfileCfgFile, true);
|
||||
let profile = loadYamlCfgFile(hdPflCfgFile, true);
|
||||
if(typeof profile !== 'object' || profile === null || Array.isArray(profile) || Object.keys(profile).length === 0){
|
||||
profile = {
|
||||
// base
|
||||
|
|
@ -316,23 +317,31 @@ const saveFuniToken = (data: {
|
|||
|
||||
const cfgDir = path.join(workingDir, 'config');
|
||||
|
||||
const isSetuped = (): boolean => {
|
||||
const fn = `${setupFile}.json`;
|
||||
if (!fs.existsSync(fn))
|
||||
return false;
|
||||
return JSON.parse(fs.readFileSync(fn).toString()).setuped;
|
||||
const getState = (): GuiState => {
|
||||
const fn = `${stateFile}.json`;
|
||||
if (!fs.existsSync(fn)) {
|
||||
return {
|
||||
"setup": false,
|
||||
"services": {}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(fn).toString());
|
||||
} catch(e) {
|
||||
console.error('Invalid state file, regenerating');
|
||||
return {
|
||||
"setup": false,
|
||||
"services": {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setSetuped = (bool: boolean) => {
|
||||
const fn = `${setupFile}.json`;
|
||||
if (bool) {
|
||||
fs.writeFileSync(fn, JSON.stringify({
|
||||
setuped: true
|
||||
}, null, 2));
|
||||
} else {
|
||||
if (fs.existsSync(fn)) {
|
||||
fs.removeSync(fn);
|
||||
}
|
||||
const setState = (state: GuiState) => {
|
||||
const fn = `${stateFile}.json`;
|
||||
try {
|
||||
fs.writeFileSync(fn, JSON.stringify(state, null, 2));
|
||||
} catch(e) {
|
||||
console.error('Failed to write state file.');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -352,10 +361,10 @@ export {
|
|||
loadHDToken,
|
||||
saveHDProfile,
|
||||
loadHDProfile,
|
||||
isSetuped,
|
||||
setSetuped,
|
||||
getState,
|
||||
setState,
|
||||
writeYamlCfgFile,
|
||||
sessCfgFile,
|
||||
hdProfileCfgFile,
|
||||
hdPflCfgFile,
|
||||
cfgDir
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "multi-downloader-nx",
|
||||
"short_name": "aniDL",
|
||||
"version": "4.2.1b1",
|
||||
"version": "4.3.0b1",
|
||||
"description": "Download videos from Funimation, Crunchyroll, or Hidive via cli",
|
||||
"keywords": [
|
||||
"download",
|
||||
|
|
|
|||
Loading…
Reference in a new issue