better code: split files

Massimo Melina committed May 1, 2022 at 18:50 UTC 3fa0c4e5d1f003c14f5ee297f3f35be45693fccd
4 files changed +154 -132
server/src/adminApis.ts
+7 -131
@@ -6,8 +6,10 @@ import { getStatus, getUrls, httpsPortCfg, portCfg } from './listen'
6 import { API_VERSION, BUILD_TIMESTAMP, COMPATIBLE_API_VERSION, FORBIDDEN, HFS_STARTED, IS_WINDOWS, VERSION } from './const'
7 import vfsApis from './api.vfs'
8 import accountsApis from './api.accounts'
9 -import { Connection, getConnections } from './connections'
10 -import { debounceAsync, isLocalHost, objSameKeys, onOff, pendingPromise, same } from './misc'
9 +import pluginsApis from './api.plugins'
10 +import monitorApis from './api.monitor'
11 +import { getConnections } from './connections'
12 +import { debounceAsync, isLocalHost, onOff } from './misc'
13 import _ from 'lodash'
14 import events from './events'
15 import { getFromAccount } from './perm'
@@ -18,23 +20,15 @@ import { writeFile } from 'fs/promises'
20 import { createReadStream } from 'fs'
21 import * as readline from 'readline'
22 import { loggers } from './log'
21 -import {
22 - mapPlugins,
23 - getAvailablePlugins,
24 - Plugin,
25 - AvailablePlugin,
26 - enablePlugins,
27 - pluginsConfig,
28 - getPluginConfigFields
29 -} from './plugins'
23 import { execFile } from 'child_process'
24 import { promisify } from 'util'
32 -import assert from 'assert'
25
26 export const adminApis: ApiHandlers = {
27
28 ...vfsApis,
29 ...accountsApis,
30 + ...pluginsApis,
31 + ...monitorApis,
32
33 async set_config({ values: v }) {
34 if (v) {
@@ -48,9 +42,7 @@ export const adminApis: ApiHandlers = {
42 return {}
43 },
44
51 - get_config(params) {
52 - return getWholeConfig(params)
53 - },
45 + get_config: getWholeConfig,
46
47 async get_status() {
48 const st = getStatus()
@@ -77,48 +69,6 @@ export const adminApis: ApiHandlers = {
69 }
70 },
71
80 - async disconnect({ ip, port, wait }) {
81 - const match = _.matches({ ip, port })
82 - const c = getConnections().find(c => match(getConnAddress(c)))
83 - const waiter = pendingPromise<void>()
84 - c?.socket.end(waiter.resolve)
85 - if (wait)
86 - await waiter
87 - return { result: Boolean(c) }
88 - },
89 -
90 - get_connections({}, ctx) {
91 - const list = sendList( getConnections().map(c => serializeConnection(c)) )
92 - return list.events(ctx, {
93 - connection: conn => list.add(serializeConnection(conn)),
94 - connectionClosed(conn: Connection) {
95 - list.remove(serializeConnection(conn, true))
96 - },
97 - connectionUpdated(conn: Connection, change: Partial<Omit<Connection,'ip'>>) {
98 - if (change.ctx) {
99 - Object.assign(change, fromCtx(change.ctx))
100 - delete change.ctx
101 - }
102 - list.update(serializeConnection(conn, true), change)
103 - },
104 - })
105 -
106 - function serializeConnection(conn: Connection, minimal?:true) {
107 - const { socket, started, secure, got } = conn
108 - return Object.assign(getConnAddress(conn), !minimal && {
109 - v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
110 - got,
111 - started,
112 - secure: (secure || undefined) as boolean|undefined, // undefined will save some space once json-ed
113 - ...fromCtx(conn.ctx),
114 - })
115 - }
116 -
117 - function fromCtx(ctx?: Koa.Context) {
118 - return ctx && { path: ctx.fileSource && ctx.path } // only for downloading files
119 - }
120 - },
121 -
72 async save_pem({ cert, private_key, name='self' }) {
73 if (!cert || !private_key)
74 return new ApiError(400)
@@ -159,80 +109,6 @@ export const adminApis: ApiHandlers = {
109 }
110 }
111 },
162 -
163 - get_plugins({}, ctx) {
164 - const list = sendList([ ...mapPlugins(serialize), ...getAvailablePlugins() ])
165 - return list.events(ctx, {
166 - pluginInstalled: p => list.add(serialize(p)),
167 - 'pluginStarted pluginStopped': p => {
168 - const { id, ...rest } = serialize(p)
169 - list.update({ id }, rest)
170 - },
171 - pluginUninstalled: id => list.remove({ id }),
172 - })
173 -
174 - function serialize(p: Readonly<Plugin> | AvailablePlugin) {
175 - return Object.assign('getData' in p ? p.getData() : p, { started: null }, _.pick(p, ['id','started']))
176 - }
177 - },
178 -
179 - async set_plugin({ id, enabled, config }) {
180 - assert(id, 'id')
181 - if (enabled !== undefined)
182 - enablePlugins.set( arr =>
183 - arr.includes(id) === enabled ? arr
184 - : enabled ? [...arr, id]
185 - : arr.filter((x: string) => x !== id)
186 - )
187 - if (config) {
188 - const fields = getPluginConfigFields(id)
189 - config = _.pickBy(config, (v, k) =>
190 - v !== null && !same(v, fields?.[k]?.defaultValue))
191 - if (_.isEmpty(config))
192 - config = undefined
193 - pluginsConfig.set(v => ({ ...v, [id]: config }))
194 - }
195 - return {}
196 - },
197 -
198 - async get_plugin({ id }) {
199 - return {
200 - enabled: enablePlugins.get().includes(id),
201 - config: {
202 - ...objSameKeys(getPluginConfigFields(id) ||{}, v => v?.defaultValue),
203 - ...pluginsConfig.get()[id]
204 - }
205 - }
206 - },
207 -}
208 -
209 -// offer an api for a generic dynamic list
210 -function sendList<T>(addAtStart: T[]=[]) {
211 - const stream = new Readable({ objectMode: true, read(){} })
212 - const ret = {
213 - return: stream,
214 - add(rec: T) { stream.push({ add: rec }) },
215 - remove(key: Partial<T>) { stream.push({ remove: [ key ] }) },
216 - update(search: Partial<T>, change: Partial<T>) {
217 - stream.push({ update:[{ search, change }] })
218 - },
219 - events(ctx: Koa.Context, eventMap: Parameters<typeof onOff>[1]) {
220 - const off = onOff(events, eventMap)
221 - ctx.res.once('close', off)
222 - return stream
223 - }
224 - }
225 - for (const x of addAtStart)
226 - ret.add(x)
227 - stream.push('init')
228 - return ret
229 -}
230 -
231 -function getConnAddress(conn: Connection) {
232 - return {
233 - ip: conn.ctx?.ip || conn.socket.remoteAddress,
234 - port: conn.socket.remotePort,
235 - }
112 }
113
114 for (const k in adminApis) {
server/src/api.monitor.ts new
+60
@@ -0,0 +1,60 @@
1 +import _ from 'lodash'
2 +import { Connection, getConnections } from './connections'
3 +import { pendingPromise } from './misc'
4 +import { ApiHandlers, sendList } from './apiMiddleware'
5 +import Koa from 'koa'
6 +
7 +const apis: ApiHandlers = {
8 +
9 + async disconnect({ ip, port, wait }) {
10 + const match = _.matches({ ip, port })
11 + const c = getConnections().find(c => match(getConnAddress(c)))
12 + const waiter = pendingPromise<void>()
13 + c?.socket.end(waiter.resolve)
14 + if (wait)
15 + await waiter
16 + return { result: Boolean(c) }
17 + },
18 +
19 + get_connections({}, ctx) {
20 + const list = sendList( getConnections().map(c => serializeConnection(c)) )
21 + return list.events(ctx, {
22 + connection: conn => list.add(serializeConnection(conn)),
23 + connectionClosed(conn: Connection) {
24 + list.remove(serializeConnection(conn, true))
25 + },
26 + connectionUpdated(conn: Connection, change: Partial<Omit<Connection,'ip'>>) {
27 + if (change.ctx) {
28 + Object.assign(change, fromCtx(change.ctx))
29 + delete change.ctx
30 + }
31 + list.update(serializeConnection(conn, true), change)
32 + },
33 + })
34 +
35 + function serializeConnection(conn: Connection, minimal?:true) {
36 + const { socket, started, secure, got } = conn
37 + return Object.assign(getConnAddress(conn), !minimal && {
38 + v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
39 + got,
40 + started,
41 + secure: (secure || undefined) as boolean|undefined, // undefined will save some space once json-ed
42 + ...fromCtx(conn.ctx),
43 + })
44 + }
45 +
46 + function fromCtx(ctx?: Koa.Context) {
47 + return ctx && { path: ctx.fileSource && ctx.path } // only for downloading files
48 + }
49 + },
50 +
51 +}
52 +
53 +export default apis
54 +
55 +function getConnAddress(conn: Connection) {
56 + return {
57 + ip: conn.ctx?.ip || conn.socket.remoteAddress,
58 + port: conn.socket.remotePort,
59 + }
60 +}
server/src/api.plugins.ts new
+62
@@ -0,0 +1,62 @@
1 +import {
2 + AvailablePlugin,
3 + enablePlugins,
4 + getAvailablePlugins,
5 + getPluginConfigFields,
6 + mapPlugins,
7 + Plugin, pluginsConfig
8 +} from './plugins'
9 +import _ from 'lodash'
10 +import assert from 'assert'
11 +import { objSameKeys, same } from './misc'
12 +import { ApiHandlers, sendList } from './apiMiddleware'
13 +
14 +const apis: ApiHandlers = {
15 + get_plugins({}, ctx) {
16 + const list = sendList([ ...mapPlugins(serialize), ...getAvailablePlugins() ])
17 + return list.events(ctx, {
18 + pluginInstalled: p => list.add(serialize(p)),
19 + 'pluginStarted pluginStopped': p => {
20 + const { id, ...rest } = serialize(p)
21 + list.update({ id }, rest)
22 + },
23 + pluginUninstalled: id => list.remove({ id }),
24 + })
25 +
26 + function serialize(p: Readonly<Plugin> | AvailablePlugin) {
27 + return Object.assign('getData' in p ? p.getData() : p, { started: null }, _.pick(p, ['id','started']))
28 + }
29 + },
30 +
31 + async set_plugin({ id, enabled, config }) {
32 + assert(id, 'id')
33 + if (enabled !== undefined)
34 + enablePlugins.set( arr =>
35 + arr.includes(id) === enabled ? arr
36 + : enabled ? [...arr, id]
37 + : arr.filter((x: string) => x !== id)
38 + )
39 + if (config) {
40 + const fields = getPluginConfigFields(id)
41 + config = _.pickBy(config, (v, k) =>
42 + v !== null && !same(v, fields?.[k]?.defaultValue))
43 + if (_.isEmpty(config))
44 + config = undefined
45 + pluginsConfig.set(v => ({ ...v, [id]: config }))
46 + }
47 + return {}
48 + },
49 +
50 + async get_plugin({ id }) {
51 + return {
52 + enabled: enablePlugins.get().includes(id),
53 + config: {
54 + ...objSameKeys(getPluginConfigFields(id) ||{}, v => v?.defaultValue),
55 + ...pluginsConfig.get()[id]
56 + }
57 + }
58 + },
59 +
60 +}
61 +
62 +export default apis
server/src/apiMiddleware.ts
+25 -1
@@ -4,7 +4,8 @@ import { IncomingMessage } from 'http'
4 import Koa from 'koa'
5 import createSSE from './sse'
6 import { Readable } from 'stream'
7 -import { asyncGeneratorToReadable } from './misc'
7 +import { asyncGeneratorToReadable, onOff } from './misc'
8 +import events from './events'
9
10 export class ApiError extends Error {
11 constructor(public status:number, message?:string | Error) {
@@ -63,3 +64,26 @@ async function getJsonFromReq(req: IncomingMessage): Promise<any> {
64 })
65 })
66 }
67 +
68 +// offer an api for a generic dynamic list
69 +export function sendList<T>(addAtStart: T[]=[]) {
70 + const stream = new Readable({ objectMode: true, read(){} })
71 + const ret = {
72 + return: stream,
73 + add(rec: T) { stream.push({ add: rec }) },
74 + remove(key: Partial<T>) { stream.push({ remove: [ key ] }) },
75 + update(search: Partial<T>, change: Partial<T>) {
76 + stream.push({ update:[{ search, change }] })
77 + },
78 + events(ctx: Koa.Context, eventMap: Parameters<typeof onOff>[1]) {
79 + const off = onOff(events, eventMap)
80 + ctx.res.once('close', off)
81 + return stream
82 + }
83 + }
84 + for (const x of addAtStart)
85 + ret.add(x)
86 + stream.push('init')
87 + return ret
88 +}
89 +