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:
AnimeDL 2023-07-15 11:16:55 -07:00
parent f7ddaf1176
commit c3dab33f6b
12 changed files with 93 additions and 53 deletions

View file

@ -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
View file

@ -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[]],

View file

@ -1,3 +0,0 @@
{
"setuped": true
}

View file

@ -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,

View file

@ -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);
});

View file

@ -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() {

View file

@ -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> {

View file

@ -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> {

View file

@ -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) {

View file

@ -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);

View file

@ -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
};

View file

@ -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",