hfs 0.26.8 → 0.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/admin/assets/index-509bb1d6.js +415 -0
- package/admin/assets/index-60a380a7.css +1 -0
- package/admin/assets/sha512-738f0943.js +8 -0
- package/admin/index.html +3 -1
- package/admin/{public/logo.svg → logo.svg} +0 -0
- package/frontend/assets/index-6e178dfd.css +1 -0
- package/frontend/assets/index-aea7654e.js +85 -0
- package/frontend/assets/sha512-bf915587.js +8 -0
- package/frontend/{public/fontello.css → fontello.css} +0 -0
- package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
- package/frontend/index.html +4 -2
- package/package.json +2 -6
- package/plugins/vhosting/plugin.js +23 -20
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +128 -0
- package/src/api.file_list.js +110 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +104 -0
- package/src/api.plugins.js +128 -0
- package/src/api.vfs.js +167 -0
- package/src/apiMiddleware.js +123 -0
- package/src/block.js +34 -0
- package/src/commands.js +125 -0
- package/src/config.js +168 -0
- package/src/connections.js +57 -0
- package/src/const.js +94 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +49 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +104 -0
- package/src/index.js +57 -0
- package/src/listen.js +235 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +195 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +183 -0
- package/src/plugins.js +343 -0
- package/src/serveFile.js +105 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +30 -0
- package/src/throttler.js +91 -0
- package/src/update.js +70 -0
- package/src/util-files.js +163 -0
- package/src/util-generators.js +31 -0
- package/src/util-http.js +32 -0
- package/src/vfs.js +232 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +73 -0
- package/admin/.DS_Store +0 -0
- package/admin/.eslintrc +0 -8
- package/admin/.gitignore +0 -23
- package/admin/package.json +0 -67
- package/admin/src/AccountForm.ts +0 -92
- package/admin/src/AccountsPage.ts +0 -143
- package/admin/src/App.ts +0 -83
- package/admin/src/ArrayField.ts +0 -84
- package/admin/src/ConfigPage.ts +0 -279
- package/admin/src/FileField.ts +0 -52
- package/admin/src/FileForm.ts +0 -148
- package/admin/src/FilePicker.ts +0 -166
- package/admin/src/HomePage.ts +0 -96
- package/admin/src/InstalledPlugins.ts +0 -158
- package/admin/src/LoginRequired.ts +0 -75
- package/admin/src/LogoutPage.ts +0 -27
- package/admin/src/LogsPage.ts +0 -75
- package/admin/src/MainMenu.ts +0 -74
- package/admin/src/MenuButton.ts +0 -38
- package/admin/src/MonitorPage.ts +0 -200
- package/admin/src/OnlinePlugins.ts +0 -101
- package/admin/src/PermField.ts +0 -80
- package/admin/src/PluginsPage.ts +0 -27
- package/admin/src/VfsMenuBar.ts +0 -58
- package/admin/src/VfsPage.ts +0 -124
- package/admin/src/VfsTree.ts +0 -95
- package/admin/src/addFiles.ts +0 -59
- package/admin/src/api.ts +0 -246
- package/admin/src/dialog.ts +0 -203
- package/admin/src/index.css +0 -21
- package/admin/src/index.ts +0 -10
- package/admin/src/md.ts +0 -31
- package/admin/src/misc.ts +0 -141
- package/admin/src/react-app-env.d.ts +0 -1
- package/admin/src/reportWebVitals.ts +0 -15
- package/admin/src/setupTests.ts +0 -5
- package/admin/src/state.ts +0 -40
- package/admin/src/theme.ts +0 -37
- package/admin/tsconfig.json +0 -26
- package/admin/vite.config.ts +0 -32
- package/frontend/.DS_Store +0 -0
- package/frontend/.eslintrc +0 -8
- package/frontend/.gitignore +0 -23
- package/frontend/package.json +0 -51
- package/frontend/src/App.ts +0 -25
- package/frontend/src/Breadcrumbs.ts +0 -43
- package/frontend/src/BrowseFiles.ts +0 -141
- package/frontend/src/Head.ts +0 -45
- package/frontend/src/UserPanel.ts +0 -52
- package/frontend/src/api.ts +0 -78
- package/frontend/src/components.ts +0 -54
- package/frontend/src/dialog.css +0 -76
- package/frontend/src/dialog.ts +0 -105
- package/frontend/src/icons.ts +0 -46
- package/frontend/src/index.scss +0 -307
- package/frontend/src/index.ts +0 -10
- package/frontend/src/login.ts +0 -50
- package/frontend/src/menu.ts +0 -188
- package/frontend/src/misc.ts +0 -54
- package/frontend/src/options.ts +0 -52
- package/frontend/src/react-app-env.d.ts +0 -1
- package/frontend/src/reportWebVitals.ts +0 -15
- package/frontend/src/setupTests.ts +0 -5
- package/frontend/src/state.ts +0 -82
- package/frontend/src/useAuthorized.ts +0 -17
- package/frontend/src/useFetchList.ts +0 -144
- package/frontend/src/useTheme.ts +0 -23
- package/frontend/tsconfig.json +0 -26
- package/frontend/vite.config.ts +0 -21
- package/src/QuickZipStream.ts +0 -279
- package/src/ThrottledStream.ts +0 -98
- package/src/adminApis.ts +0 -161
- package/src/api.accounts.ts +0 -78
- package/src/api.auth.ts +0 -131
- package/src/api.file_list.ts +0 -102
- package/src/api.helpers.ts +0 -30
- package/src/api.monitor.ts +0 -106
- package/src/api.plugins.ts +0 -139
- package/src/api.vfs.ts +0 -182
- package/src/apiMiddleware.ts +0 -124
- package/src/block.ts +0 -35
- package/src/commands.ts +0 -122
- package/src/config.ts +0 -166
- package/src/connections.ts +0 -60
- package/src/const.ts +0 -57
- package/src/crypt.ts +0 -16
- package/src/debounceAsync.ts +0 -51
- package/src/events.ts +0 -6
- package/src/frontEndApis.ts +0 -17
- package/src/github.ts +0 -102
- package/src/index.ts +0 -53
- package/src/listen.ts +0 -220
- package/src/log.ts +0 -128
- package/src/middlewares.ts +0 -176
- package/src/misc.ts +0 -149
- package/src/pbkdf2.ts +0 -83
- package/src/perm.ts +0 -194
- package/src/plugins.ts +0 -342
- package/src/serveFile.ts +0 -104
- package/src/serveGuiFiles.ts +0 -95
- package/src/sse.ts +0 -29
- package/src/throttler.ts +0 -106
- package/src/update.ts +0 -67
- package/src/util-files.ts +0 -137
- package/src/util-generators.ts +0 -29
- package/src/util-http.ts +0 -29
- package/src/vfs.ts +0 -258
- package/src/watchLoad.ts +0 -75
- package/src/zip.ts +0 -69
package/src/vfs.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import fs from 'fs/promises'
|
|
4
|
-
import { basename, join } from 'path'
|
|
5
|
-
import { isMatch } from 'micromatch'
|
|
6
|
-
import { dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, typedKeys } from './misc'
|
|
7
|
-
import Koa from 'koa'
|
|
8
|
-
import _ from 'lodash'
|
|
9
|
-
import { defineConfig, setConfig } from './config'
|
|
10
|
-
import { FORBIDDEN, IS_WINDOWS, UNAUTHORIZED } from './const'
|
|
11
|
-
import events from './events'
|
|
12
|
-
import { getCurrentUsernameExpanded } from './perm'
|
|
13
|
-
import { with_ } from './misc'
|
|
14
|
-
|
|
15
|
-
const WHO_ANYONE = true
|
|
16
|
-
const WHO_NO_ONE = false
|
|
17
|
-
const WHO_ANY_ACCOUNT = '*'
|
|
18
|
-
type AccountList = string[]
|
|
19
|
-
type Who = typeof WHO_ANYONE
|
|
20
|
-
| typeof WHO_NO_ONE
|
|
21
|
-
| typeof WHO_ANY_ACCOUNT
|
|
22
|
-
| AccountList
|
|
23
|
-
|
|
24
|
-
interface VfsPerm {
|
|
25
|
-
can_see: Who
|
|
26
|
-
can_read: Who
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
type Masks = Record<string, VfsNode>
|
|
30
|
-
|
|
31
|
-
export interface VfsNode extends Partial<VfsPerm> {
|
|
32
|
-
name?: string
|
|
33
|
-
source?: string
|
|
34
|
-
children?: VfsNode[]
|
|
35
|
-
default?: string
|
|
36
|
-
mime?: string | Record<string,string>
|
|
37
|
-
rename?: Record<string, string>
|
|
38
|
-
masks?: Masks // express fields for descendants that are not in the tree
|
|
39
|
-
// fields that are only filled at run-time
|
|
40
|
-
isTemp?: true // this node doesn't belong to the tree and was created by necessity
|
|
41
|
-
url?: string // what url brought to this node
|
|
42
|
-
parents?: VfsNode[]
|
|
43
|
-
original?: VfsNode // if this is a temp node but reflecting an existing node
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const defaultPerms: VfsPerm = {
|
|
47
|
-
can_see: WHO_ANYONE,
|
|
48
|
-
can_read: WHO_ANYONE,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const MIME_AUTO = 'auto'
|
|
52
|
-
|
|
53
|
-
function inheritFromParent(parent: VfsNode, child: VfsNode) {
|
|
54
|
-
for (const k of typedKeys(defaultPerms)) {
|
|
55
|
-
const v = parent[k]
|
|
56
|
-
if (v !== undefined)
|
|
57
|
-
child[k] ??= v
|
|
58
|
-
}
|
|
59
|
-
if (typeof parent.mime === 'object' && typeof child.mime === 'object')
|
|
60
|
-
_.defaults(child.mime, parent.mime)
|
|
61
|
-
else
|
|
62
|
-
child.mime ||= parent.mime
|
|
63
|
-
return child
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=vfs) : Promise<VfsNode | undefined> {
|
|
67
|
-
let initialSlashes = 0
|
|
68
|
-
while (url[initialSlashes] === '/')
|
|
69
|
-
initialSlashes++
|
|
70
|
-
let nextSlash = url.indexOf('/', initialSlashes)
|
|
71
|
-
const name = decodeURIComponent(url.slice(initialSlashes, nextSlash < 0 ? undefined : nextSlash))
|
|
72
|
-
if (!name)
|
|
73
|
-
return parent
|
|
74
|
-
const rest = nextSlash < 0 ? '' : url.slice(nextSlash+1, url.endsWith('/') ? -1 : undefined)
|
|
75
|
-
if (dirTraversal(name) || /[\/]/.test(name)) {
|
|
76
|
-
if (ctx)
|
|
77
|
-
ctx.status = 418
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
const parents = parent.parents || [] // don't waste time cloning the array, as we won't keep intermediate nodes
|
|
81
|
-
const ret: VfsNode = {
|
|
82
|
-
isTemp: true,
|
|
83
|
-
url: enforceFinal('/', parent.url || '') + name,
|
|
84
|
-
parents,
|
|
85
|
-
}
|
|
86
|
-
parents.push(parent)
|
|
87
|
-
inheritFromParent(parent, ret)
|
|
88
|
-
inheritMasks(ret, parent, name)
|
|
89
|
-
applyMasks(ret, parent, name)
|
|
90
|
-
// does the tree node have a child that goes by this name?
|
|
91
|
-
const sameName = !IS_WINDOWS ? (x:string) => x === name // easy
|
|
92
|
-
: with_(name.toLowerCase(), lc =>
|
|
93
|
-
(x: string) => x.toLowerCase() === lc)
|
|
94
|
-
const child = parent.children?.find(x => sameName(getNodeName(x)))
|
|
95
|
-
if (child) // yes
|
|
96
|
-
return urlToNode(rest, ctx, Object.assign(ret, child, { original: child }))
|
|
97
|
-
// not in the tree, we can see consider continuing on the disk
|
|
98
|
-
if (!parent.source) return // but then we need the current node to be linked to the disk, otherwise, we give up
|
|
99
|
-
let onDisk = name
|
|
100
|
-
if (parent.rename) { // reverse the mapping
|
|
101
|
-
for (const [from, to] of Object.entries(parent.rename))
|
|
102
|
-
if (name === to) {
|
|
103
|
-
onDisk = from
|
|
104
|
-
break // found, search no more
|
|
105
|
-
}
|
|
106
|
-
ret.rename = renameUnderPath(parent.rename, name)
|
|
107
|
-
}
|
|
108
|
-
ret.source = enforceFinal('/', parent.source) + onDisk
|
|
109
|
-
if (parent.default)
|
|
110
|
-
inheritFromParent({ mime: { '*': MIME_AUTO } }, ret)
|
|
111
|
-
if (rest)
|
|
112
|
-
return urlToNode(rest, ctx, ret)
|
|
113
|
-
if (ret.source)
|
|
114
|
-
try { await fs.stat(ret.source) } // check existence
|
|
115
|
-
catch { return }
|
|
116
|
-
return ret
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export let vfs: VfsNode = {}
|
|
120
|
-
defineConfig<VfsNode>('vfs', {}).sub(data =>
|
|
121
|
-
vfs = data)
|
|
122
|
-
|
|
123
|
-
export function saveVfs() {
|
|
124
|
-
return setConfig({ vfs: _.cloneDeep(vfs) }, true)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function getNodeName(node: VfsNode) {
|
|
128
|
-
return node.name
|
|
129
|
-
|| node.source && (
|
|
130
|
-
/^[a-zA-Z]:\\?$/.test(node.source) && node.source.slice(0, 2)
|
|
131
|
-
|| basename(node.source)
|
|
132
|
-
|| node.source
|
|
133
|
-
)
|
|
134
|
-
|| '' // should happen only for root
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export async function nodeIsDirectory(node: VfsNode) {
|
|
138
|
-
return Boolean(!node.source || await isDirectory(node.source))
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function hasPermission(node: VfsNode, perm: keyof VfsPerm, ctx: Koa.Context): boolean {
|
|
142
|
-
return matchWho(node[perm] ?? defaultPerms[perm], ctx)
|
|
143
|
-
&& (perm !== 'can_see' || hasPermission(node, 'can_read', ctx)) // for can_see you must also can_read
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export async function* walkNode(parent:VfsNode, ctx?: Koa.Context, depth:number=0, prefixPath:string=''): AsyncIterableIterator<VfsNode> {
|
|
147
|
-
const { children, source } = parent
|
|
148
|
-
if (children)
|
|
149
|
-
for (let idx = 0; idx < children.length; idx++) {
|
|
150
|
-
const child = children[idx]
|
|
151
|
-
yield* workItem({
|
|
152
|
-
...child,
|
|
153
|
-
name: prefixPath ? (prefixPath + getNodeName(child)) : child.name
|
|
154
|
-
}, depth > 0 && await nodeIsDirectory(child).catch(() => false))
|
|
155
|
-
}
|
|
156
|
-
if (!source)
|
|
157
|
-
return
|
|
158
|
-
try {
|
|
159
|
-
for await (const path of dirStream(source, depth)) {
|
|
160
|
-
if (ctx?.req.aborted)
|
|
161
|
-
return
|
|
162
|
-
const renamed = parent.rename?.[path]
|
|
163
|
-
yield* workItem({
|
|
164
|
-
name: prefixPath + (renamed || path),
|
|
165
|
-
source: join(source, path),
|
|
166
|
-
rename: renameUnderPath(parent.rename, path),
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch(e) {
|
|
171
|
-
console.debug('glob', source, e) // ENOTDIR, or lacking permissions
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// item will be changed, so be sure to pass a temp node
|
|
175
|
-
async function* workItem(item: VfsNode, recur=false) {
|
|
176
|
-
const name = getNodeName(item)
|
|
177
|
-
// we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
|
|
178
|
-
const virtualBasename = basename(name)
|
|
179
|
-
const url = enforceFinal('/', parent.url || '') + virtualBasename
|
|
180
|
-
Object.assign(item, {
|
|
181
|
-
isTemp: true,
|
|
182
|
-
url,
|
|
183
|
-
parents: [ ...parent.parents||[], parent],
|
|
184
|
-
})
|
|
185
|
-
inheritFromParent(parent, item)
|
|
186
|
-
applyMasks(item, parent, virtualBasename)
|
|
187
|
-
if (ctx && !hasPermission(item, 'can_see', ctx))
|
|
188
|
-
return
|
|
189
|
-
yield item
|
|
190
|
-
if (!recur) return
|
|
191
|
-
inheritMasks(item, parent, virtualBasename)
|
|
192
|
-
yield* walkNode(item, ctx, depth - 1, name + '/')
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function applyMasks(item: VfsNode, parent: VfsNode, virtualBasename: string) {
|
|
196
|
-
const { masks } = parent
|
|
197
|
-
if (!masks) return
|
|
198
|
-
for (const k in masks)
|
|
199
|
-
if (k.startsWith('**/') && isMatch(virtualBasename, k.slice(3))
|
|
200
|
-
|| !k.includes('/') && isMatch(virtualBasename, k))
|
|
201
|
-
Object.assign(item, masks[k])
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function inheritMasks(item: VfsNode, parent: VfsNode, virtualBasename:string) {
|
|
205
|
-
const { masks } = parent
|
|
206
|
-
if (!masks) return
|
|
207
|
-
const o: Masks = {}
|
|
208
|
-
for (const k in masks)
|
|
209
|
-
if (k.startsWith('**/'))
|
|
210
|
-
o[k.slice(3)] = masks[k]
|
|
211
|
-
else if (k.startsWith(virtualBasename+'/'))
|
|
212
|
-
o[k.slice(virtualBasename.length+1)] = masks[k]
|
|
213
|
-
if (Object.keys(o).length)
|
|
214
|
-
item.masks = o
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function renameUnderPath(rename:undefined | Record<string,string>, path: string) {
|
|
218
|
-
if (!rename) return rename
|
|
219
|
-
const match = path+'/'
|
|
220
|
-
rename = Object.fromEntries(Object.entries(rename).map(([k, v]) =>
|
|
221
|
-
[k.startsWith(match) ? k.slice(match.length) : '', v]))
|
|
222
|
-
delete rename['']
|
|
223
|
-
return _.isEmpty(rename) ? undefined : rename
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function matchWho(who: Who, ctx: Koa.Context) {
|
|
227
|
-
return who === WHO_ANYONE
|
|
228
|
-
|| who === WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
|
|
229
|
-
|| Array.isArray(who) && (() => // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
|
|
230
|
-
getOrSet(ctx.state, 'usernames', () => getCurrentUsernameExpanded(ctx)).some((u:string) =>
|
|
231
|
-
who.includes(u) ))()
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export function cantReadStatusCode(node: VfsNode) {
|
|
235
|
-
return node.can_read === false ? FORBIDDEN : UNAUTHORIZED
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
events.on('accountRenamed', (from, to) => {
|
|
239
|
-
recur(vfs)
|
|
240
|
-
saveVfs()
|
|
241
|
-
|
|
242
|
-
function recur(n: VfsNode) {
|
|
243
|
-
replace(n.can_see)
|
|
244
|
-
replace(n.can_read)
|
|
245
|
-
|
|
246
|
-
if (n.masks)
|
|
247
|
-
Object.values(n.masks).forEach(recur)
|
|
248
|
-
n.children?.forEach(recur)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function replace(a?: Who) {
|
|
252
|
-
if (!Array.isArray(a)) return
|
|
253
|
-
for (let i=0; i < a.length; i++)
|
|
254
|
-
if (a[i] === from)
|
|
255
|
-
a[i] = to
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
})
|
package/src/watchLoad.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { FSWatcher, watch } from 'fs'
|
|
4
|
-
import fs from 'fs/promises'
|
|
5
|
-
import yaml from 'yaml'
|
|
6
|
-
import { debounceAsync, readFileBusy } from './misc'
|
|
7
|
-
|
|
8
|
-
export type WatchLoadCanceller = () => void
|
|
9
|
-
|
|
10
|
-
interface Options { failedOnFirstAttempt?: ()=>void }
|
|
11
|
-
|
|
12
|
-
type WriteFile = typeof fs.writeFile
|
|
13
|
-
interface WatchLoadReturn { unwatch:WatchLoadCanceller, save:WriteFile }
|
|
14
|
-
export function watchLoad(path:string, parser:(data:any)=>void|Promise<void>, { failedOnFirstAttempt }:Options={}): WatchLoadReturn {
|
|
15
|
-
let doing = false
|
|
16
|
-
let watcher: FSWatcher | undefined
|
|
17
|
-
const debounced = debounceAsync(load, 500, { leading: true })
|
|
18
|
-
let retry: NodeJS.Timeout
|
|
19
|
-
let saving: Promise<unknown> | undefined
|
|
20
|
-
let lastStats: any
|
|
21
|
-
init().then(ok => ok || failedOnFirstAttempt?.())
|
|
22
|
-
return {
|
|
23
|
-
unwatch(){
|
|
24
|
-
watcher?.close()
|
|
25
|
-
clearTimeout(retry)
|
|
26
|
-
watcher = undefined
|
|
27
|
-
},
|
|
28
|
-
save(...args:Parameters<WriteFile>) {
|
|
29
|
-
return Promise.resolve(saving).then(() => // wait in case another is ongoing
|
|
30
|
-
saving = fs.writeFile(...args).finally(() => // save but also keep track of the current operation
|
|
31
|
-
saving = undefined)) // clear
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function init() {
|
|
36
|
-
try {
|
|
37
|
-
debounced().then()
|
|
38
|
-
watcher = watch(path, ()=> {
|
|
39
|
-
if (!saving)
|
|
40
|
-
debounced().then()
|
|
41
|
-
})
|
|
42
|
-
return true // used actually just by the first invocation
|
|
43
|
-
}
|
|
44
|
-
catch(e) {
|
|
45
|
-
retry = setTimeout(init, 3_000) // manual watching until watch is successful
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function load(){
|
|
50
|
-
if (doing) return
|
|
51
|
-
doing = true
|
|
52
|
-
let data: any
|
|
53
|
-
try {
|
|
54
|
-
try { // I've seen watch() firing 'change' without any change, so we'll check if any change is detectable before going on
|
|
55
|
-
const stats = await fs.stat(path)
|
|
56
|
-
if (stats.mtimeMs === lastStats?.mtimeMs) return
|
|
57
|
-
lastStats = stats
|
|
58
|
-
|
|
59
|
-
data = await readFileBusy(path)
|
|
60
|
-
console.debug('loaded', path)
|
|
61
|
-
}
|
|
62
|
-
catch (e: any) {
|
|
63
|
-
if (e.code === 'EPERM')
|
|
64
|
-
console.error("missing permissions on file", path) // warn user, who could be clueless about this problem
|
|
65
|
-
return // ignore read errors
|
|
66
|
-
}
|
|
67
|
-
if (path.endsWith('.yaml'))
|
|
68
|
-
data = yaml.parse(data)
|
|
69
|
-
await parser(data)
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
doing = false
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/zip.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
2
|
-
|
|
3
|
-
import { getNodeName, hasPermission, nodeIsDirectory, urlToNode, VfsNode, walkNode } from './vfs'
|
|
4
|
-
import Koa from 'koa'
|
|
5
|
-
import { filterMapGenerator, pattern2filter } from './misc'
|
|
6
|
-
import { QuickZipStream } from './QuickZipStream'
|
|
7
|
-
import { createReadStream } from 'fs'
|
|
8
|
-
import fs from 'fs/promises'
|
|
9
|
-
import { defineConfig } from './config'
|
|
10
|
-
import { dirname } from 'path'
|
|
11
|
-
import { getRange } from './serveFile'
|
|
12
|
-
|
|
13
|
-
export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
|
|
14
|
-
ctx.status = 200
|
|
15
|
-
ctx.mime = 'zip'
|
|
16
|
-
const name = getNodeName(node)
|
|
17
|
-
ctx.attachment((name || 'archive') + '.zip')
|
|
18
|
-
const filter = pattern2filter(String(ctx.query.search||''))
|
|
19
|
-
const { list } = ctx.query
|
|
20
|
-
const walker = !list ? walkNode(node, ctx, Infinity)
|
|
21
|
-
: (async function*(): AsyncIterableIterator<VfsNode> {
|
|
22
|
-
for await (const el of String(list).split('*')) { // we are using * as separator because it cannot be used in a file name and doesn't need url encoding
|
|
23
|
-
const subNode = await urlToNode(el, ctx, node)
|
|
24
|
-
if (!subNode || !hasPermission(subNode,'can_read',ctx))
|
|
25
|
-
continue
|
|
26
|
-
if (await nodeIsDirectory(subNode)) {// a directory needs to walked
|
|
27
|
-
yield* walkNode(subNode, ctx, Infinity, el + '/')
|
|
28
|
-
continue
|
|
29
|
-
}
|
|
30
|
-
let folder = dirname(el)
|
|
31
|
-
folder = folder === '.' ? '' : folder + '/'
|
|
32
|
-
yield { ...subNode, name: folder + getNodeName(subNode) } // reflect relative path in archive, otherwise way may have name-clashes
|
|
33
|
-
}
|
|
34
|
-
})()
|
|
35
|
-
const mappedWalker = filterMapGenerator(walker, async (el:VfsNode) => {
|
|
36
|
-
const { source } = el
|
|
37
|
-
const name = getNodeName(el)
|
|
38
|
-
if (!source || ctx.req.aborted || !filter(name))
|
|
39
|
-
return
|
|
40
|
-
try {
|
|
41
|
-
const st = await fs.stat(source)
|
|
42
|
-
if (!st || !st.isFile())
|
|
43
|
-
return
|
|
44
|
-
return {
|
|
45
|
-
path: name,
|
|
46
|
-
size: st.size,
|
|
47
|
-
ts: st.mtime || st.ctime,
|
|
48
|
-
mode: st.mode,
|
|
49
|
-
sourcePath: source,
|
|
50
|
-
getData: () => createReadStream(source, { start: 0 , end: Math.max(0, st.size-1) })
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
catch {}
|
|
54
|
-
})
|
|
55
|
-
const zip = new QuickZipStream(mappedWalker)
|
|
56
|
-
const time = 1000 * zipSeconds.get()
|
|
57
|
-
const size = await zip.calculateSize(time)
|
|
58
|
-
ctx.response.length = size
|
|
59
|
-
const range = getRange(ctx, size) // keep var size as ctx.response.length won't preserve a NaN
|
|
60
|
-
if (ctx.status >= 400)
|
|
61
|
-
return
|
|
62
|
-
if (range)
|
|
63
|
-
zip.applyRange(range.start, range.end)
|
|
64
|
-
ctx.body = zip
|
|
65
|
-
ctx.req.on('close', ()=> zip.destroy())
|
|
66
|
-
ctx.state.archive = 'zip'
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const zipSeconds = defineConfig('zip_calculate_size_for_seconds', 1)
|