hfs 3.1.5 → 3.2.0-beta2
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/admin/assets/af-DWEYq388.js +1 -0
- package/admin/assets/am-DgLbAgj6.js +1 -0
- package/admin/assets/ar-DgEWkO74.js +1 -0
- package/admin/assets/ar-dz-DHq--Sr8.js +1 -0
- package/admin/assets/ar-iq-D1r3nsb9.js +1 -0
- package/admin/assets/ar-kw-C85fGHwp.js +1 -0
- package/admin/assets/ar-ly-Bq6pjGjs.js +1 -0
- package/admin/assets/ar-ma-SGvmh6Mj.js +1 -0
- package/admin/assets/ar-sa-Bv8hiFi6.js +1 -0
- package/admin/assets/ar-tn-Bdvo77v3.js +1 -0
- package/admin/assets/az-7fPndoov.js +1 -0
- package/admin/assets/be-wjXeIeAK.js +1 -0
- package/admin/assets/bg-DZwBvjzH.js +1 -0
- package/admin/assets/bi-qCtxvMhO.js +1 -0
- package/admin/assets/bm-BVPWvreb.js +1 -0
- package/admin/assets/bn-CdWvye7d.js +1 -0
- package/admin/assets/bn-bd-B5-3blmz.js +1 -0
- package/admin/assets/bo-BEVcgyN2.js +1 -0
- package/admin/assets/br-DGQD6fFs.js +1 -0
- package/admin/assets/bs-So4MoRua.js +1 -0
- package/admin/assets/ca-Bk5ytG4g.js +1 -0
- package/admin/assets/cs-C4NU8eW-.js +1 -0
- package/admin/assets/cv-Ct-s-zrW.js +1 -0
- package/admin/assets/cy-ojtDPj8_.js +1 -0
- package/admin/assets/da-CYGin6vm.js +1 -0
- package/admin/assets/de-BEbJ01zH.js +1 -0
- package/admin/assets/de-at-C7UwE1rJ.js +1 -0
- package/admin/assets/de-ch-CsZ7YQc_.js +1 -0
- package/admin/assets/dv-DaVliwLd.js +1 -0
- package/admin/assets/el-DM_KqKEP.js +1 -0
- package/admin/assets/en-DedtOfaf.js +1 -0
- package/admin/assets/en-au-52Bzk5D9.js +1 -0
- package/admin/assets/en-ca-3pzEPK2N.js +1 -0
- package/admin/assets/en-gb-BrwDQS2G.js +1 -0
- package/admin/assets/en-ie-BUXSHrkL.js +1 -0
- package/admin/assets/en-il-a22drDCn.js +1 -0
- package/admin/assets/en-in-BUjecjkp.js +1 -0
- package/admin/assets/en-nz-Bbo7tnB_.js +1 -0
- package/admin/assets/en-sg-CZVDddmd.js +1 -0
- package/admin/assets/en-tt-DmSGwRia.js +1 -0
- package/admin/assets/eo-B71nkHZU.js +1 -0
- package/admin/assets/es-Dk6VCuuk.js +1 -0
- package/admin/assets/es-do-DMErY8ol.js +1 -0
- package/admin/assets/es-mx-BMRmqa3u.js +1 -0
- package/admin/assets/es-pr-CtBQz48p.js +1 -0
- package/admin/assets/es-us-CrDl5pnO.js +1 -0
- package/admin/assets/et-CO9OHqio.js +1 -0
- package/admin/assets/eu-Bip44atW.js +1 -0
- package/admin/assets/fa-CHbJ_dTM.js +1 -0
- package/admin/assets/fi-DKfoLmaQ.js +1 -0
- package/admin/assets/fo-DG1kOEfw.js +1 -0
- package/admin/assets/fr-DV73GZR4.js +1 -0
- package/admin/assets/fr-ca-BK-RoZiC.js +1 -0
- package/admin/assets/fr-ch-DjqEC5E_.js +1 -0
- package/admin/assets/fy-znrRQdeC.js +1 -0
- package/admin/assets/ga-BlZeKu0N.js +1 -0
- package/admin/assets/gd-BmrycMnC.js +1 -0
- package/admin/assets/gl-CuT8e5mi.js +1 -0
- package/admin/assets/gom-latn-BSWVd0A6.js +1 -0
- package/admin/assets/gu-BHK6LfvD.js +1 -0
- package/admin/assets/he-DPoTUevR.js +1 -0
- package/admin/assets/hi-BRuLafoW.js +1 -0
- package/admin/assets/hr-Bzge-10P.js +1 -0
- package/admin/assets/ht-Ck9BCna1.js +1 -0
- package/admin/assets/hu-CzqqbYmU.js +1 -0
- package/admin/assets/hy-am-C-eV4E8v.js +1 -0
- package/admin/assets/id-Dv8GZQvB.js +1 -0
- package/admin/assets/{index-DTxjaflW.js → index-BPIX0qPj.js} +1 -1
- package/admin/assets/index-CFWd-FDo.css +1 -0
- package/admin/assets/index-D3HviM6x.js +889 -0
- package/admin/assets/is-CK6VY3M_.js +1 -0
- package/admin/assets/it-1gtki4a5.js +1 -0
- package/admin/assets/it-ch-C0Mj3-pC.js +1 -0
- package/admin/assets/ja-Dl3AfnM1.js +1 -0
- package/admin/assets/jv-CznX-tGV.js +1 -0
- package/admin/assets/ka-BNjZxCug.js +1 -0
- package/admin/assets/kk-80J_xldf.js +1 -0
- package/admin/assets/km-CuWChDRB.js +1 -0
- package/admin/assets/kn-BZp_PBdl.js +1 -0
- package/admin/assets/ko-RirpyUl_.js +1 -0
- package/admin/assets/ku-Dz8ACD5w.js +1 -0
- package/admin/assets/ky-BN3ylOhj.js +1 -0
- package/admin/assets/lb-D7h_YoEn.js +1 -0
- package/admin/assets/lo-BrlbTUPD.js +1 -0
- package/admin/assets/lt-cXuHFdTa.js +1 -0
- package/admin/assets/lv-CjIpv13Q.js +1 -0
- package/admin/assets/me-B-jTh39Z.js +1 -0
- package/admin/assets/mi-D06xdVmt.js +1 -0
- package/admin/assets/mk-iLbuxyOf.js +1 -0
- package/admin/assets/ml-2W0y6Zb2.js +1 -0
- package/admin/assets/mn-6zbvKjeb.js +1 -0
- package/admin/assets/mr-7-jrgLyw.js +1 -0
- package/admin/assets/ms-CRH6rEXt.js +1 -0
- package/admin/assets/ms-my-BIJmu2S-.js +1 -0
- package/admin/assets/mt-CbXmxK-D.js +1 -0
- package/admin/assets/my-BvMUsxU8.js +1 -0
- package/admin/assets/nb-DvKHgF7L.js +1 -0
- package/admin/assets/ne-DiZZ3Lm6.js +1 -0
- package/admin/assets/nl-be-Dy7PYRbC.js +1 -0
- package/admin/assets/nl-t_2A_VAT.js +1 -0
- package/admin/assets/nn-Cw7EwosO.js +1 -0
- package/admin/assets/oc-lnc-CuFfB75K.js +1 -0
- package/admin/assets/pa-in-DOFyZ-Ft.js +1 -0
- package/admin/assets/pl-BD7FyCJj.js +1 -0
- package/admin/assets/pt-DSKLLE_u.js +1 -0
- package/admin/assets/pt-br-BhL4gb5Z.js +1 -0
- package/admin/assets/rn-DoHoZZPd.js +1 -0
- package/admin/assets/ro-B0v-_lH0.js +1 -0
- package/admin/assets/ru-BMVOk5eA.js +1 -0
- package/admin/assets/rw-CWF0w6eL.js +1 -0
- package/admin/assets/sd-DO2rrjch.js +1 -0
- package/admin/assets/se-CAolO9WQ.js +1 -0
- package/admin/assets/{sha512-D936QW8l.js → sha512-ZlUYj4Hr.js} +1 -1
- package/admin/assets/si-D03dHfb5.js +1 -0
- package/admin/assets/sk-_GcZGaN3.js +1 -0
- package/admin/assets/sl-Cb1lUGab.js +1 -0
- package/admin/assets/sq-Czzt23Tr.js +1 -0
- package/admin/assets/sr-D76dVqKJ.js +1 -0
- package/admin/assets/sr-cyrl-CuoFbJjW.js +1 -0
- package/admin/assets/ss-g-fGaM29.js +1 -0
- package/admin/assets/sv-CZxc8I45.js +1 -0
- package/admin/assets/sv-fi-D8REJeLz.js +1 -0
- package/admin/assets/sw-B1n3PjWG.js +1 -0
- package/admin/assets/ta-K5mexJNT.js +1 -0
- package/admin/assets/te-DT6dj5B6.js +1 -0
- package/admin/assets/tet-DXwYNm_H.js +1 -0
- package/admin/assets/tg-BCcZKcE2.js +1 -0
- package/admin/assets/th-CeeeseFX.js +1 -0
- package/admin/assets/tk-CJ6KW44d.js +1 -0
- package/admin/assets/tl-ph-DzE8lDmm.js +1 -0
- package/admin/assets/tlh-ER6KiMxG.js +1 -0
- package/admin/assets/tr-D48NGNpr.js +1 -0
- package/admin/assets/tzl-5AsmDTYM.js +1 -0
- package/admin/assets/tzm-2AzZ1YPf.js +1 -0
- package/admin/assets/tzm-latn-Cd5hPQuT.js +1 -0
- package/admin/assets/ug-cn-DU-MZ9Vx.js +1 -0
- package/admin/assets/uk-hn6vcOkn.js +1 -0
- package/admin/assets/ur-jOObtqB6.js +1 -0
- package/admin/assets/uz-BlftYfHF.js +1 -0
- package/admin/assets/uz-latn-BpuI0ccM.js +1 -0
- package/admin/assets/vi-DVo3LusT.js +1 -0
- package/admin/assets/x-pseudo-BuVzNhqi.js +1 -0
- package/admin/assets/yo-BvWGwb4m.js +1 -0
- package/admin/assets/zh-D9-tfba1.js +1 -0
- package/admin/assets/zh-cn-CFL5sbIW.js +1 -0
- package/admin/assets/zh-hk-DRP8u65r.js +1 -0
- package/admin/assets/zh-tw-CtVs1ihI.js +1 -0
- package/admin/index.html +2 -2
- package/frontend/assets/index-legacy-D3BTBYs5.js +9 -0
- package/frontend/assets/{index-legacy-vmpqwZZf.js → index-legacy-DcrWtKxQ.js} +1 -1
- package/frontend/assets/{sha512-legacy-wI89-UHR.js → sha512-legacy-DJvEwScE.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +144 -71
- package/package.json +9 -9
- package/plugins/antibrute/plugin.js +150 -19
- package/plugins/list-uploader/public/main.js +1 -1
- package/src/acme.js +11 -7
- package/src/api.accounts.js +3 -3
- package/src/api.auth.js +3 -1
- package/src/api.get_file_list.js +17 -13
- package/src/api.monitor.js +47 -43
- package/src/api.net.js +4 -3
- package/src/api.vfs.js +1 -1
- package/src/basicWeb.js +1 -1
- package/src/commands.js +54 -1
- package/src/comments.js +7 -4
- package/src/config.js +9 -5
- package/src/consoleLog.js +39 -1
- package/src/const.js +4 -0
- package/src/cross.js +33 -6
- package/src/errorPages.js +20 -10
- package/src/events.js +3 -3
- package/src/expiringCache.js +8 -7
- package/src/fileAttr.js +73 -15
- package/src/frontEndApis.js +20 -15
- package/src/index.js +2 -1
- package/src/langs/hfs-lang-ar.json +2 -1
- package/src/langs/hfs-lang-bg.json +2 -1
- package/src/langs/hfs-lang-de.json +2 -1
- package/src/langs/hfs-lang-el.json +2 -1
- package/src/langs/hfs-lang-es.json +2 -1
- package/src/langs/hfs-lang-fi.json +2 -1
- package/src/langs/hfs-lang-fr.json +2 -1
- package/src/langs/hfs-lang-hu.json +2 -1
- package/src/langs/hfs-lang-it.json +2 -1
- package/src/langs/hfs-lang-ja.json +2 -1
- package/src/langs/hfs-lang-ko.json +2 -1
- package/src/langs/hfs-lang-lt.json +2 -1
- package/src/langs/hfs-lang-ms.json +2 -0
- package/src/langs/hfs-lang-nl.json +2 -1
- package/src/langs/hfs-lang-pt-br.json +2 -1
- package/src/langs/hfs-lang-ro.json +2 -1
- package/src/langs/hfs-lang-ru.json +2 -1
- package/src/langs/hfs-lang-sr-latn.json +2 -1
- package/src/langs/hfs-lang-sr.json +2 -1
- package/src/langs/hfs-lang-th.json +2 -1
- package/src/langs/hfs-lang-tr.json +2 -1
- package/src/langs/hfs-lang-uk.json +2 -1
- package/src/langs/hfs-lang-vi.json +2 -1
- package/src/langs/hfs-lang-zh-tw.json +2 -1
- package/src/langs/hfs-lang-zh.json +2 -1
- package/src/listen.js +2 -1
- package/src/log.js +27 -2
- package/src/middlewares.js +8 -2
- package/src/misc.js +14 -0
- package/src/nat.js +61 -21
- package/src/outboundProxy.js +1 -1
- package/src/perm.js +5 -1
- package/src/plugins.js +34 -5
- package/src/roots.js +1 -1
- package/src/selfCheck.js +2 -1
- package/src/serveGuiAndSharedFiles.js +18 -7
- package/src/serveGuiFiles.js +2 -1
- package/src/update.js +10 -18
- package/src/urlList.js +32 -0
- package/src/util-files.js +24 -9
- package/src/util-http.js +4 -0
- package/src/vfs.js +21 -9
- package/src/walkDir.js +7 -1
- package/src/webdav.js +4 -1
- package/src/zip.js +3 -2
- package/admin/assets/index-B66w-a0v.css +0 -1
- package/admin/assets/index-Df6vYR7s.js +0 -822
- package/frontend/assets/index-legacy-emBsvICj.js +0 -9
package/src/cross.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.VFS_STORED_KEYS = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = exports.WEBSITE = void 0;
|
|
20
|
+
exports.VFS_STORED_KEYS = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ADMIN = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = exports.WEBSITE = void 0;
|
|
21
21
|
exports.isWhoObject = isWhoObject;
|
|
22
22
|
exports.formatBytes = formatBytes;
|
|
23
23
|
exports.formatSpeed = formatSpeed;
|
|
@@ -34,8 +34,10 @@ exports.removeStarting = removeStarting;
|
|
|
34
34
|
exports.strinsert = strinsert;
|
|
35
35
|
exports.splitAt = splitAt;
|
|
36
36
|
exports.stringAfter = stringAfter;
|
|
37
|
+
exports.stringBefore = stringBefore;
|
|
37
38
|
exports.truthy = truthy;
|
|
38
39
|
exports.onlyTruthy = onlyTruthy;
|
|
40
|
+
exports.countUniqueBy = countUniqueBy;
|
|
39
41
|
exports.setHidden = setHidden;
|
|
40
42
|
exports.try_ = try_;
|
|
41
43
|
exports.with_ = with_;
|
|
@@ -79,6 +81,7 @@ exports.escapeHTML = escapeHTML;
|
|
|
79
81
|
exports.promiseBestEffort = promiseBestEffort;
|
|
80
82
|
exports.pathEncode = pathEncode;
|
|
81
83
|
exports.pathDecode = pathDecode;
|
|
84
|
+
exports.pathDecodeSegments = pathDecodeSegments;
|
|
82
85
|
exports.runAt = runAt;
|
|
83
86
|
exports.makeMatcher = makeMatcher;
|
|
84
87
|
exports.matches = matches;
|
|
@@ -125,18 +128,19 @@ exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknow
|
|
|
125
128
|
'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
|
|
126
129
|
'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
|
|
127
130
|
'force_lang', 'suspend_plugins', 'base_url', 'size_1024', 'disable_custom_html', 'comments_storage',
|
|
128
|
-
'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy']);
|
|
131
|
+
'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy', 'mapped_port', 'upnp_enabled', 'show_uploader']);
|
|
129
132
|
exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
|
|
130
133
|
exports.WHO_ANYONE = true;
|
|
131
134
|
exports.WHO_NO_ONE = false;
|
|
132
135
|
exports.WHO_ANY_ACCOUNT = '*';
|
|
136
|
+
exports.WHO_ADMIN = 'admin';
|
|
133
137
|
exports.defaultPerms = {
|
|
134
|
-
can_see: 'can_read',
|
|
135
138
|
can_read: exports.WHO_ANYONE,
|
|
139
|
+
can_see: 'can_read',
|
|
136
140
|
can_list: 'can_read',
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
can_archive: 'can_read',
|
|
142
|
+
can_upload: exports.WHO_ADMIN,
|
|
143
|
+
can_delete: exports.WHO_ADMIN,
|
|
140
144
|
};
|
|
141
145
|
exports.PERM_KEYS = typedKeys(exports.defaultPerms);
|
|
142
146
|
exports.VFS_STORED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename',
|
|
@@ -215,12 +219,31 @@ function stringAfter(sub, all) {
|
|
|
215
219
|
const i = all.indexOf(sub);
|
|
216
220
|
return i < 0 ? '' : all.slice(i + sub.length);
|
|
217
221
|
}
|
|
222
|
+
function stringBefore(sub, all, returnEmptyWhenSubMissing = true) {
|
|
223
|
+
const i = all.indexOf(sub);
|
|
224
|
+
return i >= 0 ? all.slice(0, i + sub.length - 1) : returnEmptyWhenSubMissing ? '' : all;
|
|
225
|
+
}
|
|
218
226
|
function truthy(value) {
|
|
219
227
|
return Boolean(value);
|
|
220
228
|
}
|
|
221
229
|
function onlyTruthy(arr) {
|
|
222
230
|
return arr.filter(truthy);
|
|
223
231
|
}
|
|
232
|
+
function countUniqueBy(items, keyFn, predicate) {
|
|
233
|
+
// use a Set so unique counting stays linear even on very large live lists
|
|
234
|
+
const seen = new Set();
|
|
235
|
+
let count = 0;
|
|
236
|
+
for (const item of items) {
|
|
237
|
+
if (predicate && !predicate(item))
|
|
238
|
+
continue;
|
|
239
|
+
const key = keyFn(item);
|
|
240
|
+
if (seen.has(key))
|
|
241
|
+
continue;
|
|
242
|
+
seen.add(key);
|
|
243
|
+
count++;
|
|
244
|
+
}
|
|
245
|
+
return count;
|
|
246
|
+
}
|
|
224
247
|
function setHidden(dest, src) {
|
|
225
248
|
return Object.defineProperties(dest, newObj(src, value => ({
|
|
226
249
|
enumerable: false,
|
|
@@ -457,6 +480,10 @@ function pathEncode(s, all = false) {
|
|
|
457
480
|
function pathDecode(s) {
|
|
458
481
|
return decodeURI(s).replace(/%23/g, '#');
|
|
459
482
|
}
|
|
483
|
+
function pathDecodeSegments(s, map = String) {
|
|
484
|
+
// decode segment by segment so reserved escapes are decoded without turning encoded slashes into separators
|
|
485
|
+
return s.split('/').map(x => map(safeDecodeURIComponent(x)).replaceAll('/', '%2F')).join('/');
|
|
486
|
+
}
|
|
460
487
|
// run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays
|
|
461
488
|
function runAt(ts, cb) {
|
|
462
489
|
let cancel = false;
|
package/src/errorPages.js
CHANGED
|
@@ -14,18 +14,28 @@ async function sendErrorPage(ctx, code = ctx.status) {
|
|
|
14
14
|
ctx.type = 'text';
|
|
15
15
|
ctx.set('content-disposition', ''); // reset ctx.attachment (or forceDownload)
|
|
16
16
|
ctx.status = code;
|
|
17
|
-
|
|
17
|
+
let msg = cross_1.HTTP_MESSAGES[ctx.status] || '';
|
|
18
18
|
if (!msg)
|
|
19
19
|
return;
|
|
20
20
|
const lang = await (0, lang_1.getLangData)(ctx);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!errorPage)
|
|
27
|
-
return;
|
|
28
|
-
if (errorPage.includes('<'))
|
|
21
|
+
const trans = lang ? Object.values(lang)[0]?.translate : undefined;
|
|
22
|
+
msg = trans?.[msg] ?? msg;
|
|
23
|
+
const page = (0, customHtml_1.getSection)(ctx.status === cross_1.HTTP_UNAUTHORIZED ? 'unauthorized' : String(ctx.status))
|
|
24
|
+
|| SIMPLE_PAGE || '';
|
|
25
|
+
if (page.includes('<'))
|
|
29
26
|
ctx.type = 'html';
|
|
30
|
-
ctx.body =
|
|
27
|
+
ctx.body = (0, cross_1.replace)(page, { MESSAGE: msg, HOME: trans?.home ?? 'home', URL: ctx.state.revProxyPath || '/' }, '$');
|
|
31
28
|
}
|
|
29
|
+
const SIMPLE_PAGE = `<!DOCTYPE html>
|
|
30
|
+
<html><head>
|
|
31
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
32
|
+
<title>$MESSAGE</title>
|
|
33
|
+
<style>
|
|
34
|
+
body{margin-top:30vh; text-align:center; font-family:sans-serif;}
|
|
35
|
+
a{color:#68a}
|
|
36
|
+
@media (prefers-color-scheme:dark){body{background:#111; color:#999;}}
|
|
37
|
+
</style>
|
|
38
|
+
</head><body>
|
|
39
|
+
<h1>$MESSAGE</h1>
|
|
40
|
+
<h2><a href="$URL">$HOME</a></h2>
|
|
41
|
+
</body></html>`;
|
package/src/events.js
CHANGED
|
@@ -5,8 +5,7 @@ exports.BetterEventEmitter = void 0;
|
|
|
5
5
|
const LISTENERS_SUFFIX = '\0listeners';
|
|
6
6
|
class BetterEventEmitter {
|
|
7
7
|
listeners = new Map();
|
|
8
|
-
|
|
9
|
-
stop = this.preventDefault; // legacy pre-0.54 (introduced in 0.53)
|
|
8
|
+
stop = Symbol();
|
|
10
9
|
on(event, listener, { warnAfter = 10, callNow = false } = {}) {
|
|
11
10
|
if (typeof event === 'string')
|
|
12
11
|
event = [event];
|
|
@@ -67,6 +66,7 @@ class BetterEventEmitter {
|
|
|
67
66
|
const output = [];
|
|
68
67
|
let prevented = false;
|
|
69
68
|
const extra = {
|
|
69
|
+
event,
|
|
70
70
|
output,
|
|
71
71
|
preventDefault() { prevented = true; }
|
|
72
72
|
};
|
|
@@ -88,7 +88,7 @@ class BetterEventEmitter {
|
|
|
88
88
|
const asyncRet = await Promise.all(syncRet);
|
|
89
89
|
return Object.assign(asyncRet, {
|
|
90
90
|
isDefaultPrevented: () => syncRet.isDefaultPrevented()
|
|
91
|
-
|| asyncRet.some((r) => r === this.
|
|
91
|
+
|| asyncRet.some((r) => r === this.stop)
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
}
|
package/src/expiringCache.js
CHANGED
|
@@ -6,24 +6,25 @@ function expiringCache(ttlMs) {
|
|
|
6
6
|
throw Error('invalid TTL');
|
|
7
7
|
const o = new Map();
|
|
8
8
|
return Object.assign(o, {
|
|
9
|
-
|
|
9
|
+
invalidate,
|
|
10
|
+
// creator can return undefined if the value should not be cached
|
|
10
11
|
try(k, creator) {
|
|
11
12
|
let ret = o.get(k);
|
|
12
13
|
if (ret === undefined) { // undefined = missing, as we don't accept this value in our cache
|
|
13
|
-
ret = creator(
|
|
14
|
+
ret = creator(k);
|
|
14
15
|
if (ret !== undefined) {
|
|
15
16
|
o.set(k, ret);
|
|
16
17
|
Promise.resolve(ret).then(v => {
|
|
17
18
|
if (v === undefined) // even in a promise, we'll consider undefined as a request to cancel the caching
|
|
18
|
-
invalidate();
|
|
19
|
+
invalidate(k);
|
|
19
20
|
}, () => { }) // avoid js warning
|
|
20
|
-
.finally(() => setTimeout(invalidate, ttlMs)); // wait for async (in case) before starting the timer
|
|
21
|
-
}
|
|
22
|
-
function invalidate() {
|
|
23
|
-
o.delete(k);
|
|
21
|
+
.finally(() => setTimeout(() => invalidate(k), ttlMs)); // wait for async (in case) before starting the timer
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
return ret;
|
|
27
25
|
},
|
|
28
26
|
});
|
|
27
|
+
function invalidate(k) {
|
|
28
|
+
o.delete(k);
|
|
29
|
+
}
|
|
29
30
|
}
|
package/src/fileAttr.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileAttrDb = void 0;
|
|
3
4
|
exports.storeFileAttr = storeFileAttr;
|
|
4
5
|
exports.loadFileAttr = loadFileAttr;
|
|
5
6
|
exports.purgeFileAttr = purgeFileAttr;
|
|
7
|
+
exports.moveStoredFileAttrs = moveStoredFileAttrs;
|
|
8
|
+
exports.deleteStoredFileAttrs = deleteStoredFileAttrs;
|
|
6
9
|
const kvstorage_1 = require("@rejetto/kvstorage");
|
|
7
10
|
const util_1 = require("util");
|
|
8
11
|
const promises_1 = require("fs/promises");
|
|
@@ -11,15 +14,17 @@ const first_1 = require("./first");
|
|
|
11
14
|
const promises_2 = require("node:fs/promises");
|
|
12
15
|
const util_files_1 = require("./util-files");
|
|
13
16
|
const const_1 = require("./const");
|
|
17
|
+
const path_1 = require("path");
|
|
14
18
|
const fsx = (0, cross_1.try_)(() => {
|
|
15
19
|
const lib = require('fs-x-attributes');
|
|
16
20
|
return { set: (0, util_1.promisify)(lib.set), get: (0, util_1.promisify)(lib.get) };
|
|
17
21
|
}, () => console.warn('fs-x-attributes not available'));
|
|
18
|
-
const fileAttrDb = new kvstorage_1.KvStorage({ defaultPutDelay: 1000, maxPutDelay: 5000 });
|
|
19
|
-
(0, first_1.onProcessExit)(() => fileAttrDb.close());
|
|
20
22
|
const FN = 'file-attr.kv';
|
|
21
|
-
|
|
23
|
+
exports.fileAttrDb = new kvstorage_1.KvStorage({ defaultPutDelay: 1000, maxPutDelay: 5000 });
|
|
24
|
+
(0, first_1.onProcessExit)(() => exports.fileAttrDb.close());
|
|
25
|
+
exports.fileAttrDb.open(FN).catch(e => console.error(String(e)));
|
|
22
26
|
const FILE_ATTR_PREFIX = 'user.hfs.'; // user. prefix to be linux compatible
|
|
27
|
+
const FILE_ATTR_KEY_SEPARATOR = '|';
|
|
23
28
|
/* @param v must be JSON-able or undefined */
|
|
24
29
|
async function storeFileAttr(path, k, v) {
|
|
25
30
|
const s = await (0, util_files_1.statWithTimeout)(path).catch(() => null);
|
|
@@ -30,29 +35,82 @@ async function storeFileAttr(path, k, v) {
|
|
|
30
35
|
return true;
|
|
31
36
|
}
|
|
32
37
|
// fallback to our kv-storage
|
|
33
|
-
|
|
34
|
-
if (!s && !v)
|
|
35
|
-
return; // file was probably deleted, and we were asked to remove a possible attribute, but there's no fileAttrDb, so we are done, don't create the db file for nothing
|
|
36
|
-
else
|
|
37
|
-
await fileAttrDb.open(FN);
|
|
38
|
-
// pipe should be a safe separator
|
|
39
|
-
return await fileAttrDb.put(`${path}|${k}`, v)?.catch((e) => {
|
|
38
|
+
return await exports.fileAttrDb.put(fileAttrKey(path, k), v)?.catch((e) => {
|
|
40
39
|
console.error("Couldn't store metadata on", path, String(e.message || e));
|
|
41
40
|
return false;
|
|
42
41
|
}) ?? true; // if put is undefined, the value was already there
|
|
43
42
|
}
|
|
44
43
|
async function loadFileAttr(path, k) {
|
|
45
44
|
return await fsx?.get(path, FILE_ATTR_PREFIX + k)
|
|
46
|
-
.then((x) => x === '' ? undefined : (0, cross_1.tryJson)(String(x)), () => fileAttrDb.isOpen() ? fileAttrDb.get(
|
|
45
|
+
.then((x) => x === '' ? undefined : (0, cross_1.tryJson)(String(x)), () => exports.fileAttrDb.isOpen() ? exports.fileAttrDb.get(fileAttrKey(path, k)).catch(console.error) : null)
|
|
47
46
|
?? undefined; // normalize, as we get null instead of undefined on windows
|
|
48
47
|
}
|
|
49
48
|
async function purgeFileAttr() {
|
|
50
49
|
let n = 0;
|
|
51
|
-
await Promise.all(Array.from(fileAttrDb.keys()).map(k => {
|
|
52
|
-
const
|
|
53
|
-
return fn && (0, promises_1.access)(fn).catch(() => n++ && void fileAttrDb.del(k));
|
|
50
|
+
await Promise.all(Array.from(exports.fileAttrDb.keys()).map(k => {
|
|
51
|
+
const fn = splitFileAttrKey(k)?.filePath;
|
|
52
|
+
return fn && (0, promises_1.access)(fn).catch(() => n++ && void exports.fileAttrDb.del(k));
|
|
54
53
|
}));
|
|
55
54
|
if (n)
|
|
56
|
-
await fileAttrDb.rewrite();
|
|
55
|
+
await exports.fileAttrDb.rewrite();
|
|
57
56
|
console.log(`Removed ${n} entrie(s)`);
|
|
58
57
|
}
|
|
58
|
+
async function moveStoredFileAttrs(fromPath, toPath) {
|
|
59
|
+
try {
|
|
60
|
+
if (fromPath === toPath || !exports.fileAttrDb.isOpen())
|
|
61
|
+
return;
|
|
62
|
+
const entries = storedFileAttrEntries();
|
|
63
|
+
const affectedEntries = entries.filter(x => isSameOrInside(fromPath, x.filePath));
|
|
64
|
+
if (!affectedEntries.length)
|
|
65
|
+
return;
|
|
66
|
+
const affectedWithValues = await Promise.all(affectedEntries.map(async (x) => ({
|
|
67
|
+
...x,
|
|
68
|
+
value: await exports.fileAttrDb.get(x.key)
|
|
69
|
+
})));
|
|
70
|
+
const oldDestinationKeys = entries.filter(x => isSameOrInside(toPath, x.filePath)).map(x => x.key);
|
|
71
|
+
// destination attrs must be cleared first because a replaced file may not have all attrs owned by the source
|
|
72
|
+
await Promise.all(oldDestinationKeys.map(k => exports.fileAttrDb.del(k)));
|
|
73
|
+
await Promise.all(affectedWithValues.map(async ({ key, filePath, attr, value }) => {
|
|
74
|
+
const rel = (0, path_1.relative)(fromPath, filePath);
|
|
75
|
+
// physical path keys the fallback DB, so filesystem moves must carry descendant entries explicitly
|
|
76
|
+
await exports.fileAttrDb.put(fileAttrKey((0, path_1.join)(toPath, rel), attr), value);
|
|
77
|
+
await exports.fileAttrDb.del(key);
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
// metadata sync runs after the filesystem mutation, so it must not report the completed file operation as failed
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error("Couldn't move metadata in file-attr DB", fromPath, toPath, String(e.message || e));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function deleteStoredFileAttrs(path) {
|
|
86
|
+
try {
|
|
87
|
+
if (!exports.fileAttrDb.isOpen())
|
|
88
|
+
return;
|
|
89
|
+
const keys = storedFileAttrEntries().filter(x => isSameOrInside(path, x.filePath)).map(x => x.key);
|
|
90
|
+
await Promise.all(keys.map(k => exports.fileAttrDb.del(k)));
|
|
91
|
+
}
|
|
92
|
+
// metadata cleanup runs after deletion, so surfacing this would leave clients seeing a false delete failure
|
|
93
|
+
catch (e) {
|
|
94
|
+
console.error("Couldn't delete metadata from file-attr DB", path, String(e.message || e));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function storedFileAttrEntries() {
|
|
98
|
+
return (0, cross_1.onlyTruthy)(Array.from(exports.fileAttrDb.keys()).map(splitFileAttrKey));
|
|
99
|
+
}
|
|
100
|
+
function splitFileAttrKey(key) {
|
|
101
|
+
const i = key.lastIndexOf(FILE_ATTR_KEY_SEPARATOR);
|
|
102
|
+
if (i < 0)
|
|
103
|
+
return;
|
|
104
|
+
return {
|
|
105
|
+
key,
|
|
106
|
+
filePath: key.slice(0, i),
|
|
107
|
+
attr: key.slice(i + 1)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function fileAttrKey(path, attr) {
|
|
111
|
+
return path + FILE_ATTR_KEY_SEPARATOR + attr;
|
|
112
|
+
}
|
|
113
|
+
function isSameOrInside(parent, path) {
|
|
114
|
+
const rel = (0, path_1.relative)(parent, path);
|
|
115
|
+
return rel === '' || Boolean(rel) && !rel.startsWith('..') && !(0, path_1.isAbsolute)(rel);
|
|
116
|
+
}
|
package/src/frontEndApis.js
CHANGED
|
@@ -20,11 +20,13 @@ const promises_1 = require("fs/promises");
|
|
|
20
20
|
const path_1 = require("path");
|
|
21
21
|
const upload_1 = require("./upload");
|
|
22
22
|
const misc_1 = require("./misc");
|
|
23
|
+
const config_1 = require("./config");
|
|
23
24
|
const comments_1 = require("./comments");
|
|
24
25
|
const SendList_1 = require("./SendList");
|
|
25
26
|
const adminApis_1 = require("./adminApis");
|
|
26
27
|
const lodash_1 = __importDefault(require("lodash"));
|
|
27
28
|
const partialFolderSize = {};
|
|
29
|
+
const showUploader = (0, config_1.defineConfig)(misc_1.CFG.show_uploader, misc_1.WHO_ADMIN);
|
|
28
30
|
exports.frontEndApis = {
|
|
29
31
|
get_file_list: api_get_file_list_1.get_file_list,
|
|
30
32
|
...api_auth_1.authApis,
|
|
@@ -43,19 +45,20 @@ exports.frontEndApis = {
|
|
|
43
45
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris');
|
|
44
46
|
const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx);
|
|
45
47
|
return {
|
|
46
|
-
details:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
details: (0, vfs_1.simpleWhoToError)(showUploader.get(), ctx) ? [] // return early because at the moment we only have the uploader
|
|
49
|
+
: await Promise.all(uris.map(async (uri) => {
|
|
50
|
+
if (typeof uri !== 'string')
|
|
51
|
+
return false; // false means error
|
|
52
|
+
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
53
|
+
if (!node || !(0, vfs_1.hasPermission)(node, 'can_see', ctx))
|
|
54
|
+
return false;
|
|
55
|
+
let upload = node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined);
|
|
56
|
+
if (!upload)
|
|
57
|
+
return;
|
|
58
|
+
if (!isAdmin)
|
|
59
|
+
upload = lodash_1.default.omit(upload, 'ip');
|
|
60
|
+
return { upload };
|
|
61
|
+
}))
|
|
59
62
|
};
|
|
60
63
|
},
|
|
61
64
|
async create_folder({ uri, name }, ctx) {
|
|
@@ -126,7 +129,7 @@ exports.frontEndApis = {
|
|
|
126
129
|
await (0, comments_1.setCommentFor)(node.source, comment);
|
|
127
130
|
return {};
|
|
128
131
|
},
|
|
129
|
-
async get_folder_size_partial({ id }
|
|
132
|
+
async get_folder_size_partial({ id }) {
|
|
130
133
|
(0, misc_1.apiAssertTypes)({ string: { id } });
|
|
131
134
|
return partialFolderSize[id] || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
132
135
|
},
|
|
@@ -195,7 +198,8 @@ async function moveFiles(uri_from, uri_to, ctx, override) {
|
|
|
195
198
|
throw e; // exdev = different drive
|
|
196
199
|
await (0, promises_1.copyFile)(src, dest);
|
|
197
200
|
await (0, promises_1.unlink)(src);
|
|
198
|
-
}).
|
|
201
|
+
}).then(() => (0, misc_1.moveStoredFileAttrs)(src, dest))
|
|
202
|
+
.catch(e => e.code || String(e));
|
|
199
203
|
}))
|
|
200
204
|
};
|
|
201
205
|
}
|
|
@@ -216,6 +220,7 @@ async function requestedRename(node, newName, ctx) {
|
|
|
216
220
|
try {
|
|
217
221
|
const destSource = (0, path_1.join)((0, path_1.dirname)(node.source), newName);
|
|
218
222
|
await (0, promises_1.rename)(node.source, destSource);
|
|
223
|
+
await (0, misc_1.moveStoredFileAttrs)(node.source, destSource);
|
|
219
224
|
(0, comments_1.getCommentFor)(node.source).then(c => {
|
|
220
225
|
if (!c)
|
|
221
226
|
return;
|
package/src/index.js
CHANGED
|
@@ -39,13 +39,13 @@ if (new config_1.Version(process.versions.node).olderThan('18.15.0')) {
|
|
|
39
39
|
process.exit(2);
|
|
40
40
|
}
|
|
41
41
|
process.title = 'HFS ' + const_1.VERSION;
|
|
42
|
+
misc_1.httpStream.defaultUA = 'HFS';
|
|
42
43
|
const keys = process.env.COOKIE_SIGN_KEYS?.split(',')
|
|
43
44
|
|| [(0, misc_1.randomId)(30)]; // randomness at start gives some extra security, btu also invalidates existing sessions
|
|
44
45
|
exports.app = new koa_1.default({ keys });
|
|
45
46
|
exports.app.use(middlewares_1.sessionMiddleware)
|
|
46
47
|
.use(selfCheck_1.selfCheckMiddleware)
|
|
47
48
|
.use(acme_1.acmeMiddleware)
|
|
48
|
-
.use(middlewares_1.someSecurity)
|
|
49
49
|
.use(middlewares_1.prepareState)
|
|
50
50
|
.use(geo_1.geoFilter)
|
|
51
51
|
.use(ips_1.trackIpsMw)
|
|
@@ -54,6 +54,7 @@ exports.app.use(middlewares_1.sessionMiddleware)
|
|
|
54
54
|
.use(middlewares_1.headRequests)
|
|
55
55
|
.use(roots_1.rootsMiddleware)
|
|
56
56
|
.use(log_1.logMw)
|
|
57
|
+
.use(middlewares_1.someSecurity)
|
|
57
58
|
.use(throttler_1.throttler)
|
|
58
59
|
.use(plugins_1.pluginsMiddleware)
|
|
59
60
|
.use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "إنشاء مجلد",
|
|
58
58
|
"Pick files": "اختر ملفات",
|
|
59
59
|
"Pick folder": "اختر مجلدًا",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# file} other{# files}}, {size}، جاهز للرفع",
|
|
61
|
+
"Send": "إرسال",
|
|
61
62
|
"Clear": "مسح",
|
|
62
63
|
"failed_upload": "تعذر رفع {name}",
|
|
63
64
|
"confirm_resume": "استئناف الرفع؟",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"Create folder": "Създай папка",
|
|
57
57
|
"Pick files": "Избери файлове",
|
|
58
58
|
"Pick folder": "Избери папка",
|
|
59
|
-
"
|
|
59
|
+
"ready_to_upload": "{n,plural,one{# файл} other{# файла}}, {size}, готови за качване",
|
|
60
|
+
"Send": "Изпрати",
|
|
60
61
|
"Clear": "Изчисти",
|
|
61
62
|
"failed_upload": "Неуспешно качване на {name}",
|
|
62
63
|
"file too large": "файлът е твърде голям",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Ordner erstellen",
|
|
58
58
|
"Pick files": "Dateien auswählen",
|
|
59
59
|
"Pick folder": "Ordner auswählen",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# Datei} other{# Dateien}}, {size}, bereit zum Hochladen",
|
|
61
|
+
"Send": "Senden",
|
|
61
62
|
"Clear": "Löschen",
|
|
62
63
|
"failed_upload": "Konnte {name} nicht hochladen.",
|
|
63
64
|
"confirm_resume": "Upload fortsetzen?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Δημιουργία φακέλου",
|
|
58
58
|
"Pick files": "Επιλογή αρχείων",
|
|
59
59
|
"Pick folder": "Επιλογή φακέλου",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# αρχείο} other{# αρχεία}}, {size}, έτοιμα για αποστολή",
|
|
61
|
+
"Send": "Αποστολή",
|
|
61
62
|
"Clear": "Καθαρισμός",
|
|
62
63
|
"failed_upload": "Αποτυχία μεταφόρτωσης του {name}",
|
|
63
64
|
"confirm_resume": "Συνέχεια της μεταφόρτωσης;",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Crear carpeta",
|
|
58
58
|
"Pick files": "Seleccionar archivo",
|
|
59
59
|
"Pick folder": "Seleccionar carpeta",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# archivo} other{# archivos}}, {size}, listo para subir",
|
|
61
|
+
"Send": "Enviar",
|
|
61
62
|
"Clear": "Limpiar",
|
|
62
63
|
"failed_upload": "No se puede subir {name}",
|
|
63
64
|
"confirm_resume": "Continuar subida?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Tee uusi kansio",
|
|
58
58
|
"Pick files": "Valitse lähetettävät tiedostot",
|
|
59
59
|
"Pick folder": "Valitse lähetettävä kansio",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# tiedosto} other{# tiedostot}}, {size}, valmis ladattavaksi",
|
|
61
|
+
"Send": "Lähetä",
|
|
61
62
|
"Clear": "Tyhjennä",
|
|
62
63
|
"failed_upload": "Lähetys epäonnistui {name}",
|
|
63
64
|
"confirm_resume": "Jatketaanko lähetystä?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Créez un dossier",
|
|
58
58
|
"Pick files": "Choisissez les fichiers",
|
|
59
59
|
"Pick folder": "Choisissez un dossier",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# fichier} other{# fichiers}}, {size}, prêt à téléverser",
|
|
61
|
+
"Send": "Envoyer",
|
|
61
62
|
"Clear": "Effacer",
|
|
62
63
|
"failed_upload": "Impossible de transférer {name}",
|
|
63
64
|
"confirm_resume": "Reprendre le télé-versement?",
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"Create folder": "Mappa létrehozása",
|
|
59
59
|
"Pick files": "Fájlok kiválasztása",
|
|
60
60
|
"Pick folder": "Mappa kiválasztása",
|
|
61
|
-
"
|
|
61
|
+
"ready_to_upload": "{n,plural,one{# fájl} other{# fájl}}, {size}, feltöltésre kész",
|
|
62
|
+
"Send": "Küldés",
|
|
62
63
|
"Clear": "Mégsem",
|
|
63
64
|
"failed_upload": "A(z) {name} feltöltése sikertelen",
|
|
64
65
|
"confirm_resume": "Folytatja a feltöltést?",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"Create folder": "Crea cartella",
|
|
56
56
|
"Pick files": "Scegli file",
|
|
57
57
|
"Pick folder": "Scegli una cartella",
|
|
58
|
-
"
|
|
58
|
+
"ready_to_upload": "{n} file, {size}, pronto per il caricamento",
|
|
59
|
+
"Send": "Invia",
|
|
59
60
|
"Clear": "Azzera",
|
|
60
61
|
"failed_upload": "Upload fallito per {name}",
|
|
61
62
|
"file too large": "file troppo grande",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "新しいフォルダ",
|
|
58
58
|
"Pick files": "ファイルを選択",
|
|
59
59
|
"Pick folder": "フォルダを選択",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# ファイル} other{# ファイル}}、{size}、アップロード準備完了",
|
|
61
|
+
"Send": "送信",
|
|
61
62
|
"Clear": "クリア",
|
|
62
63
|
"failed_upload": "{name}をアップロードできませんでした",
|
|
63
64
|
"confirm_resume": "アップロードを再開しますか?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "폴더 생성",
|
|
58
58
|
"Pick files": "파일 선택",
|
|
59
59
|
"Pick folder": "폴더 선택",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# file} other{# files}}, {size}, 업로드 준비 완료",
|
|
61
|
+
"Send": "보내기",
|
|
61
62
|
"Clear": "지우기",
|
|
62
63
|
"failed_upload": "{name} 업로드에 실패했습니다.",
|
|
63
64
|
"confirm_resume": "업로드를 재시도 하시겠습니까?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Sukurti aplanką",
|
|
58
58
|
"Pick files": "Pasirinkti failus",
|
|
59
59
|
"Pick folder": "Pasirinkti aplanką",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# failas} few{# failai} other{# failų}}, {size}, paruošta įkelti",
|
|
61
|
+
"Send": "Siųsti",
|
|
61
62
|
"Clear": "Išvalyti",
|
|
62
63
|
"failed_upload": "Nepavyko įkelti {name}",
|
|
63
64
|
"file too large": "failas per didelis",
|
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
"upload_finished": "{n} selesai ({size})",
|
|
60
60
|
"upload_errors": "{n} gagal",
|
|
61
61
|
"upload_file_rejected": "Beberapa fail tidak diterima",
|
|
62
|
+
"ready_to_upload": "{n,plural,one{# fail} other{# fail}}, {size}, sedia untuk dimuat naik",
|
|
63
|
+
"Send": "Hantar",
|
|
62
64
|
"download counter": "pengira muat turun",
|
|
63
65
|
"File menu": "Menu fail",
|
|
64
66
|
"Folder menu": "Menu folder",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Creeer map",
|
|
58
58
|
"Pick files": "Kies bestand",
|
|
59
59
|
"Pick folder": "Kies map",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# bestand} other{# bestanden}}, {size}, klaar om te uploaden",
|
|
61
|
+
"Send": "Verzenden",
|
|
61
62
|
"Clear": "Clear",
|
|
62
63
|
"failed_upload": "Kon het niet uploaden {name}",
|
|
63
64
|
"file too large": "bestand te groot",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Criar pasta",
|
|
58
58
|
"Pick files": "Escolher arquivos",
|
|
59
59
|
"Pick folder": "Escolher pastas",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# arquivo} other{# arquivos}}, {size}, pronto para enviar",
|
|
61
|
+
"Send": "Enviar",
|
|
61
62
|
"Clear": "Limpar",
|
|
62
63
|
"failed_upload": "Não foi possivel enviar o arquivo {name}",
|
|
63
64
|
"file too large": "arquivo é muito grande",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Creează folder",
|
|
58
58
|
"Pick files": "Selectează fișiere",
|
|
59
59
|
"Pick folder": "Selectează folder",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# fișier} other{# fișiere}}, {size}, gata de încărcat",
|
|
61
|
+
"Send": "Trimite",
|
|
61
62
|
"Clear": "Șterge",
|
|
62
63
|
"failed_upload": "Nu s-a putut încărca {name}",
|
|
63
64
|
"confirm_resume": "Continuă încărcarea?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Создать папку",
|
|
58
58
|
"Pick files": "Выбрать файлы",
|
|
59
59
|
"Pick folder": "Выбрать папку",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural, one{# файл} few{# файла} many{# файлов}}, {size}, готово к загрузке",
|
|
61
|
+
"Send": "Загрузить",
|
|
61
62
|
"Clear": "Очистить",
|
|
62
63
|
"failed_upload": "Не удалась загрузить {name}",
|
|
63
64
|
"confirm_resume": "Возобновить загрузку?",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"Create folder": "Napravi fasciklu",
|
|
58
58
|
"Pick files": "Izaberite datoteke",
|
|
59
59
|
"Pick folder": "Izaberite fasciklu",
|
|
60
|
-
"
|
|
60
|
+
"ready_to_upload": "{n,plural,one{# datoteka} other{# datoteke}}, {size}, spremno za otpremanje",
|
|
61
|
+
"Send": "Pošalji",
|
|
61
62
|
"Clear": "Očisti",
|
|
62
63
|
"failed_upload": "Nije moguće otpremiti {name}",
|
|
63
64
|
"file too large": "datoteka je prevelika",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"Create folder": "Креирај фолдер",
|
|
60
60
|
"Pick files": " Изаберите документ(а)",
|
|
61
61
|
"Pick folder": "Изаберите фолдер(е)",
|
|
62
|
-
"
|
|
62
|
+
"ready_to_upload": "{n} документ(а), {size}, спремно за отпремање",
|
|
63
|
+
"Send": "Пошаљи",
|
|
63
64
|
"Clear": "Обриши",
|
|
64
65
|
"failed_upload": "Отпремање није успело {name}",
|
|
65
66
|
"confirm_resume": "Настави преузимање?",
|