hfs 3.2.2 → 3.3.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/AccountsPage-DB7ljNHT.js +3 -0
- package/admin/assets/ArrayField-Qj2iHD7I.js +2 -0
- package/admin/assets/ConfigFilePage-TErmt0PK.js +4 -0
- package/admin/assets/CustomHtmlPage-CVmk-_JS.js +2 -0
- package/admin/assets/DataTable-DmnQXyFO.js +119 -0
- package/admin/assets/DateTimeField-BUWd2w9-.js +1 -0
- package/admin/assets/EventManager-9vwKg8as.js +1 -0
- package/admin/assets/FileField-DoBaSTxF.js +117 -0
- package/admin/assets/InternetPage-Biuer1tb.js +8 -0
- package/admin/assets/LangPage-Vd24DNK5.js +2 -0
- package/admin/assets/LogsPage-BdvttEqM.js +3 -0
- package/admin/assets/MenuButton-CHLgqU5W.js +1 -0
- package/admin/assets/MonitorPage-DlXkZUmT.js +2 -0
- package/admin/assets/OptionsPage-Zi8FmrjE.js +3 -0
- package/admin/assets/PluginsPage-BzwEIFt5.js +1 -0
- package/admin/assets/Tabs-CVipqhB9.js +1 -0
- package/admin/assets/TextEditor-BeS-mR2I.css +1 -0
- package/admin/assets/TextEditor-clbVugYM.js +34 -0
- package/admin/assets/VfsPathField-YUCV9PfL.js +1 -0
- package/admin/assets/{af-BDoqNBMg.js → af-B1Gi_bpo.js} +1 -1
- package/admin/assets/agentIcons-DW9QViyx.js +1 -0
- package/admin/assets/{am-DXBvKJ3X.js → am-B-dZnhsM.js} +1 -1
- package/admin/assets/{ar-BJIoxKTh.js → ar-DsQGp6kZ.js} +1 -1
- package/admin/assets/{ar-dz-C9l3uc_3.js → ar-dz-CxlYiKtP.js} +1 -1
- package/admin/assets/{ar-iq-DKH8u2lX.js → ar-iq-y4_Puo4B.js} +1 -1
- package/admin/assets/{ar-kw-D3gJalQp.js → ar-kw-DTOZ0VC7.js} +1 -1
- package/admin/assets/{ar-ly-BZJzWFeR.js → ar-ly-lPV9gApv.js} +1 -1
- package/admin/assets/{ar-ma-DabiOCr0.js → ar-ma-DZqLSpeB.js} +1 -1
- package/admin/assets/{ar-sa-C7suzSCY.js → ar-sa-v-5vsIOR.js} +1 -1
- package/admin/assets/{ar-tn-BvBucEx0.js → ar-tn-a4q8lhXy.js} +1 -1
- package/admin/assets/{az-CPUppH2M.js → az-Cj4yq-j-.js} +1 -1
- package/admin/assets/baseUrl-Do5CYuBZ.js +2 -0
- package/admin/assets/{be-vzvhX2dl.js → be-B-3QlFM8.js} +1 -1
- package/admin/assets/{bg-09Xbj_Y3.js → bg-A8-93xdK.js} +1 -1
- package/admin/assets/{bi-CvhiuiE9.js → bi-bwwEWGG2.js} +1 -1
- package/admin/assets/{bm-DtJAZAkZ.js → bm-DpLmtYXC.js} +1 -1
- package/admin/assets/{bn-Dnzh2x6I.js → bn-DPL_BgLw.js} +1 -1
- package/admin/assets/{bn-bd-xEy4797g.js → bn-bd-DukmY_R5.js} +1 -1
- package/admin/assets/{bo-vuPmptG3.js → bo-68KDEKGI.js} +1 -1
- package/admin/assets/{br-C9xH4OD3.js → br-gA86uwqY.js} +1 -1
- package/admin/assets/{bs-BB_uCUp1.js → bs-SfthSp42.js} +1 -1
- package/admin/assets/{ca-D7LKaf98.js → ca-8qUtZKFe.js} +1 -1
- package/admin/assets/{cs-DMFTNdNC.js → cs-CTHcqPCX.js} +1 -1
- package/admin/assets/{cv-DNUjJwnO.js → cv-BhBW7hcO.js} +1 -1
- package/admin/assets/{cy-qmAOTPwC.js → cy-BYTS4PNY.js} +1 -1
- package/admin/assets/{da-DDX54iM9.js → da-BiwfOrRd.js} +1 -1
- package/admin/assets/{de-BBmlsfbM.js → de-BFqyDxvo.js} +1 -1
- package/admin/assets/{de-at-CuMZqYfu.js → de-at-C7zUXXVL.js} +1 -1
- package/admin/assets/{de-ch-s8OXKb-5.js → de-ch-BVHosAzt.js} +1 -1
- package/admin/assets/dist-DC2A6cD2.js +1 -0
- package/admin/assets/{dv-Cjs3QDBN.js → dv-dvig99nB.js} +1 -1
- package/admin/assets/{el-BsAOrUDP.js → el-BtJcYjoT.js} +1 -1
- package/admin/assets/en-B26lXzkx.js +1 -0
- package/admin/assets/{en-au-8tMHM2OT.js → en-au-CWzJ565h.js} +1 -1
- package/admin/assets/{en-ca-c9jM4M_8.js → en-ca-nou5bZ6q.js} +1 -1
- package/admin/assets/{en-in-D2ULzXMp.js → en-gb-kl3giCnh.js} +1 -1
- package/admin/assets/{en-ie-D4_e3kV6.js → en-ie-C55e3apX.js} +1 -1
- package/admin/assets/{en-il-BoaZOven.js → en-il-HYabE6j7.js} +1 -1
- package/admin/assets/{en-tt-Cn72OR4N.js → en-in-Bri93cNM.js} +1 -1
- package/admin/assets/{en-nz-grrmB3dg.js → en-nz-BqhpzeuX.js} +1 -1
- package/admin/assets/{en-sg-Cmqxpoir.js → en-sg-q8xv-C8V.js} +1 -1
- package/admin/assets/{en-gb-unFIXx9f.js → en-tt-BqzQXPA5.js} +1 -1
- package/admin/assets/{eo-Cqxmc0TT.js → eo-5Y962E4S.js} +1 -1
- package/admin/assets/{es-CkjKdFZ3.js → es-DOQ7swrW.js} +1 -1
- package/admin/assets/{es-do-CTcTshup.js → es-do-Diob7juh.js} +1 -1
- package/admin/assets/{es-mx-DMXgMMxs.js → es-mx-miy7RFDh.js} +1 -1
- package/admin/assets/{es-pr-DNCV91L3.js → es-pr-CZa5Iw2n.js} +1 -1
- package/admin/assets/{es-us-C7o2mlIH.js → es-us-BX03CBQf.js} +1 -1
- package/admin/assets/{esm-DvUbRuYx.js → esm-Dtaf0rWK.js} +1 -1
- package/admin/assets/{et-DaPpH-gK.js → et-DGWOmMlC.js} +1 -1
- package/admin/assets/{eu-DfKyFruu.js → eu-D1Qj8RMh.js} +1 -1
- package/admin/assets/{fa-xWrBjePb.js → fa--TfwKnl9.js} +1 -1
- package/admin/assets/{fi-BOWDMsDS.js → fi-Dcbkv925.js} +1 -1
- package/admin/assets/{fo-B3FupW8R.js → fo-BPGX_WF4.js} +1 -1
- package/admin/assets/{fr-DZBDBkwp.js → fr-CNsDHzNh.js} +1 -1
- package/admin/assets/{fr-ca-CRePPJJ5.js → fr-ca-BsNs3KIJ.js} +1 -1
- package/admin/assets/{fr-ch-pFKvYU8D.js → fr-ch-BBmdtvlB.js} +1 -1
- package/admin/assets/{fy-0k7RzHUH.js → fy-q3UE-mqp.js} +1 -1
- package/admin/assets/{ga-D31rB2ys.js → ga-C-DOgnYh.js} +1 -1
- package/admin/assets/{gd-CbgY-FFE.js → gd-mLMfry_l.js} +1 -1
- package/admin/assets/{gl-DrtvS9gt.js → gl-CO8Bj9I_.js} +1 -1
- package/admin/assets/{gom-latn-Dkke3GyE.js → gom-latn-myglMmHV.js} +1 -1
- package/admin/assets/{gu-BlEipYhR.js → gu-Bdz1OpMU.js} +1 -1
- package/admin/assets/{he-BrHT6OLB.js → he-C4lqk2AA.js} +1 -1
- package/admin/assets/{hi-Bx0zlAgr.js → hi-h0Byc_UN.js} +1 -1
- package/admin/assets/{hr-CoFYGbr6.js → hr-CZV7Za1n.js} +1 -1
- package/admin/assets/{ht-Df5xKo4a.js → ht-Dg2blNvl.js} +1 -1
- package/admin/assets/{hu-BFTN3q6-.js → hu-DbOVhd9K.js} +1 -1
- package/admin/assets/{hy-am-DuZTJQed.js → hy-am-E48Q7kWE.js} +1 -1
- package/admin/assets/icons-3rupj9Xr.js +1 -0
- package/admin/assets/{id-DBZ5nPkf.js → id-Cd4yWevW.js} +1 -1
- package/admin/assets/{index-eeRX25da.css → index-11n5z0Ez.css} +1 -1
- package/admin/assets/index-Dsq-sVu9.js +477 -0
- package/admin/assets/{is-CmeHm47B.js → is-BJZYSfrA.js} +1 -1
- package/admin/assets/{it-Cv3fjOA9.js → it-BRaqqFz4.js} +1 -1
- package/admin/assets/{it-ch-DCTyHA3T.js → it-ch-BkLqxaff.js} +1 -1
- package/admin/assets/{ja-DHNVXAM5.js → ja-X_I-25cD.js} +1 -1
- package/admin/assets/{jv-BdNIxhWv.js → jv-BBBx3R3v.js} +1 -1
- package/admin/assets/{ka-D-joD0QB.js → ka-D9Yrujqk.js} +1 -1
- package/admin/assets/{kk-8wRNNjxz.js → kk-Bz450_o8.js} +1 -1
- package/admin/assets/{km-dUWOg1uK.js → km-CKOAqvHM.js} +1 -1
- package/admin/assets/{kn-CwQbzel0.js → kn-k2xTBR7a.js} +1 -1
- package/admin/assets/{ko-D_gn9Sfw.js → ko-BCvoCYib.js} +1 -1
- package/admin/assets/{ku-Dh3MRML3.js → ku-D6Rloctr.js} +1 -1
- package/admin/assets/{ky-CWYuRvil.js → ky-C54qnEzk.js} +1 -1
- package/admin/assets/{lb-Cc2xsSIx.js → lb-y3rkrWB2.js} +1 -1
- package/admin/assets/{lo-CkwNFDBz.js → lo-nA_dPnOU.js} +1 -1
- package/admin/assets/{lt-HWyFDLXu.js → lt-hjPmCCML.js} +1 -1
- package/admin/assets/{lv-C7Zx3OLO.js → lv-Bh4qmUa_.js} +1 -1
- package/admin/assets/{me-DH3AqnfF.js → me-e32suC_n.js} +1 -1
- package/admin/assets/{mi-DMmUry6m.js → mi-Dwbsuyr9.js} +1 -1
- package/admin/assets/{mk-D6MNZrzX.js → mk-DjUXwKRn.js} +1 -1
- package/admin/assets/{ml-B_ur8Zqf.js → ml-CWjFAViF.js} +1 -1
- package/admin/assets/{mn-DeL8Ry7x.js → mn-BU4qTvEl.js} +1 -1
- package/admin/assets/{mr-C01qRXFL.js → mr-Bvnduzfb.js} +1 -1
- package/admin/assets/{ms-DFUZJzOm.js → ms-XuKVAni7.js} +1 -1
- package/admin/assets/{ms-my-CUYHkebs.js → ms-my-CDbnYp0g.js} +1 -1
- package/admin/assets/{mt-CE8t5jSj.js → mt-PDuxJf8J.js} +1 -1
- package/admin/assets/{my-Df96A8jZ.js → my-7N6SHnsD.js} +1 -1
- package/admin/assets/{nb-D0837ewu.js → nb-Cu3KUtTR.js} +1 -1
- package/admin/assets/{ne-BdK5TR0R.js → ne-CYW0uI2a.js} +1 -1
- package/admin/assets/{nl-oYKmSma6.js → nl-DL7cw699.js} +1 -1
- package/admin/assets/{nl-be-BfVC0cur.js → nl-be-B3NSF-2w.js} +1 -1
- package/admin/assets/{nn-IEUPnyc8.js → nn-BHITVkGB.js} +1 -1
- package/admin/assets/{oc-lnc-g_wSIROr.js → oc-lnc-dp3c4Bty.js} +1 -1
- package/admin/assets/{pa-in-gWhcLmZE.js → pa-in-BxgIlP1T.js} +1 -1
- package/admin/assets/{pl-H3-LoxVn.js → pl-GrG6yjkB.js} +1 -1
- package/admin/assets/{pt-br-B8UcNR6_.js → pt-br-BxTgeF95.js} +1 -1
- package/admin/assets/{pt-hiX8RqVw.js → pt-cVbj6hL6.js} +1 -1
- package/admin/assets/{rn-BKupViLr.js → rn-DiVJ2_Jy.js} +1 -1
- package/admin/assets/{ro-DmhgIvml.js → ro-D1WeRyXR.js} +1 -1
- package/admin/assets/rolldown-runtime-CacB07dV.js +1 -0
- package/admin/assets/routing-Cjyek7O_.js +1 -0
- package/admin/assets/{ru-CcpO-b8g.js → ru-DZCu4V9W.js} +1 -1
- package/admin/assets/{rw-DWX_5ZK0.js → rw-DfFpVc_g.js} +1 -1
- package/admin/assets/{sd-Dat8YgLw.js → sd-yDyBYxtQ.js} +1 -1
- package/admin/assets/{se-nTBYCwx3.js → se-EJKibocp.js} +1 -1
- package/admin/assets/sha512-DJtGRwVu.js +1 -0
- package/admin/assets/{si-DG88w5El.js → si-BCBV00rO.js} +1 -1
- package/admin/assets/{sk-s5FxW36J.js → sk-B2VvG_mo.js} +1 -1
- package/admin/assets/{sl-B-HyGrpA.js → sl-BTYmt65M.js} +1 -1
- package/admin/assets/{sq-DPFtfEC8.js → sq-BaLGKAHu.js} +1 -1
- package/admin/assets/{sr-bBPg1zz4.js → sr-DarE-95b.js} +1 -1
- package/admin/assets/{sr-cyrl-cXkYHMcP.js → sr-cyrl-CrwXFtkw.js} +1 -1
- package/admin/assets/{ss-7nSqXJN7.js → ss-CbBe1RUs.js} +1 -1
- package/admin/assets/{sv-B8mG1QK4.js → sv-Baw7Je9j.js} +1 -1
- package/admin/assets/{sv-fi-CotGRXSH.js → sv-fi-CVHORXkw.js} +1 -1
- package/admin/assets/{sw-fAxU-Uck.js → sw-k1H_OzIi.js} +1 -1
- package/admin/assets/{ta-Dl0Z1pan.js → ta-Bc4ZaZT9.js} +1 -1
- package/admin/assets/{te-DmcKasFF.js → te-ClciACZi.js} +1 -1
- package/admin/assets/{tet-D6mmvYpQ.js → tet-B75w1oSG.js} +1 -1
- package/admin/assets/{tg-39Hf2-oO.js → tg-CuH2ARRk.js} +1 -1
- package/admin/assets/{th-BSmTJSFN.js → th-LvExAoJ6.js} +1 -1
- package/admin/assets/{tk-m85E5igC.js → tk-2CFwTMKo.js} +1 -1
- package/admin/assets/{tl-ph-BlYxVvNt.js → tl-ph-CNJ_lViV.js} +1 -1
- package/admin/assets/{tlh-D1POnIeE.js → tlh-9UbKQkHo.js} +1 -1
- package/admin/assets/{tr-BurtfrLy.js → tr-Cu4P4OMe.js} +1 -1
- package/admin/assets/{tzl-BvyD0lOf.js → tzl-D97WsZW-.js} +1 -1
- package/admin/assets/{tzm-DZHS0dK6.js → tzm-CURkYgMY.js} +1 -1
- package/admin/assets/{tzm-latn-yNW7CNCH.js → tzm-latn-BRGsWK-9.js} +1 -1
- package/admin/assets/{ug-cn-rxs2pf5Y.js → ug-cn-Cd4exKOc.js} +1 -1
- package/admin/assets/{uk-CgZM-NnA.js → uk-DEXM3PIH.js} +1 -1
- package/admin/assets/{ur-YxIR8yYD.js → ur-DpUycj07.js} +1 -1
- package/admin/assets/{uz-CE9QsQMG.js → uz-CznjzufA.js} +1 -1
- package/admin/assets/{uz-latn-DiVdSW6G.js → uz-latn-Baga58FN.js} +1 -1
- package/admin/assets/{vi-DKARV6qf.js → vi-BrZWcL2C.js} +1 -1
- package/admin/assets/{x-pseudo-BuAdOrXQ.js → x-pseudo-BY6J4I7_.js} +1 -1
- package/admin/assets/{yo-BcB85DhC.js → yo-Dkeaym0x.js} +1 -1
- package/admin/assets/{zh-DLmxfxzl.js → zh-C2fOZUKR.js} +1 -1
- package/admin/assets/{zh-cn-ILRADZJi.js → zh-cn-CiA7hMZm.js} +1 -1
- package/admin/assets/{zh-hk-fT7fQk9_.js → zh-hk-EkoniWh0.js} +1 -1
- package/admin/assets/{zh-tw-NqRXGP6v.js → zh-tw-KDgos-kB.js} +1 -1
- package/admin/index.html +3 -2
- package/frontend/assets/{dist-legacy-BPToXNXu.js → dist-legacy-BNFW04Ss.js} +1 -1
- package/frontend/assets/index-legacy-2akkos6t.js +1 -0
- package/frontend/assets/{lib-legacy-CBk-_G90.js → lib-legacy-CO5spnaN.js} +1 -1
- package/frontend/assets/{sha512-legacy-CWrR2rkQ.js → sha512-legacy-BuJAJxVV.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +6 -6
- package/package.json +1 -1
- package/src/AsapStream.js +18 -14
- package/src/SendList.js +42 -16
- package/src/ThrottledStream.js +3 -3
- package/src/acme.js +23 -10
- package/src/adminApis.js +7 -6
- package/src/api.accounts.js +11 -7
- package/src/api.auth.js +8 -5
- package/src/api.cert.js +1 -1
- package/src/api.get_file_list.js +14 -11
- package/src/api.lang.js +3 -2
- package/src/api.monitor.js +1 -1
- package/src/api.net.js +3 -1
- package/src/api.plugins.js +2 -0
- package/src/api.vfs.js +9 -12
- package/src/apiMiddleware.js +5 -1
- package/src/basicWeb.js +3 -2
- package/src/block.js +1 -1
- package/src/commands.js +6 -5
- package/src/comments.js +29 -13
- package/src/config.js +7 -4
- package/src/consoleLog.js +14 -12
- package/src/const.js +1 -1
- package/src/cross-const.js +27 -1
- package/src/cross.js +37 -34
- package/src/ddns.js +1 -4
- package/src/events.js +1 -4
- package/src/fileAttr.js +1 -1
- package/src/frontEndApis.js +7 -3
- package/src/geo.js +8 -2
- package/src/github.js +10 -9
- package/src/index.js +2 -1
- package/src/ips.js +24 -3
- package/src/langs/hfs-lang-ar.json +1 -0
- package/src/langs/hfs-lang-bg.json +1 -0
- package/src/langs/hfs-lang-de.json +1 -0
- package/src/langs/hfs-lang-el.json +1 -0
- package/src/langs/hfs-lang-es.json +1 -0
- package/src/langs/hfs-lang-fi.json +1 -0
- package/src/langs/hfs-lang-fr.json +1 -0
- package/src/langs/hfs-lang-hu.json +1 -0
- package/src/langs/hfs-lang-it.json +1 -0
- package/src/langs/hfs-lang-ja.json +1 -0
- package/src/langs/hfs-lang-ko.json +1 -0
- package/src/langs/hfs-lang-lt.json +1 -0
- package/src/langs/hfs-lang-ms.json +1 -0
- package/src/langs/hfs-lang-nl.json +1 -0
- package/src/langs/hfs-lang-pl.json +1 -0
- package/src/langs/hfs-lang-pt-br.json +1 -0
- package/src/langs/hfs-lang-ro.json +1 -0
- package/src/langs/hfs-lang-ru.json +1 -0
- package/src/langs/hfs-lang-sr-latn.json +1 -0
- package/src/langs/hfs-lang-sr.json +1 -0
- package/src/langs/hfs-lang-th.json +1 -0
- package/src/langs/hfs-lang-tr.json +1 -0
- package/src/langs/hfs-lang-uk.json +1 -0
- package/src/langs/hfs-lang-vi.json +1 -0
- package/src/langs/hfs-lang-zh-tw.json +1 -0
- package/src/langs/hfs-lang-zh.json +1 -0
- package/src/listen.js +17 -13
- package/src/log.js +11 -2
- package/src/middlewares.js +4 -4
- package/src/misc.js +3 -3
- package/src/multipartUpload.js +1 -2
- package/src/nat.js +1 -1
- package/src/outboundProxy.js +7 -0
- package/src/perm.js +5 -3
- package/src/plugins.js +60 -37
- package/src/selfCheck.js +4 -3
- package/src/serveFile.js +3 -3
- package/src/serveGuiAndSharedFiles.js +7 -6
- package/src/serveGuiFiles.js +15 -2
- package/src/serverCode.js +27 -0
- package/src/throttler.js +3 -2
- package/src/update.js +9 -3
- package/src/upload.js +29 -11
- package/src/uploadOwners.js +95 -0
- package/src/util-files.js +4 -2
- package/src/util-http.js +3 -1
- package/src/vfs.js +67 -30
- package/src/walkDir.js +24 -11
- package/src/watchLoad.js +13 -2
- package/src/webdav.js +33 -31
- package/src/zip.js +1 -1
- package/admin/assets/dist-CHP3cW_n.js +0 -1
- package/admin/assets/en-DQ460ET7.js +0 -1
- package/admin/assets/index-BGIe1xaR.js +0 -760
- package/admin/assets/sha512-CZz40GwW.js +0 -1
- package/frontend/assets/index-legacy-CmusDwvl.js +0 -1
package/src/throttler.js
CHANGED
|
@@ -15,11 +15,11 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
15
15
|
const events_1 = __importDefault(require("./events"));
|
|
16
16
|
const persistence_1 = require("./persistence");
|
|
17
17
|
const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
|
|
18
|
-
(0, config_1.defineConfig)(
|
|
18
|
+
(0, config_1.defineConfig)(misc_1.CFG.max_kbps, Infinity).sub(v => mainThrottleGroup.updateLimit(v));
|
|
19
19
|
const ip2group = {};
|
|
20
20
|
const SymThrStr = Symbol('stream');
|
|
21
21
|
const SymTimeout = Symbol('timeout');
|
|
22
|
-
const maxKbpsPerIp = (0, config_1.defineConfig)(
|
|
22
|
+
const maxKbpsPerIp = (0, config_1.defineConfig)(misc_1.CFG.max_kbps_per_ip, Infinity);
|
|
23
23
|
maxKbpsPerIp.sub(v => {
|
|
24
24
|
for (const [ip, { group }] of Object.entries(ip2group))
|
|
25
25
|
if (ip) // empty-string = unlimited group
|
|
@@ -71,6 +71,7 @@ const throttler = async (ctx, next) => {
|
|
|
71
71
|
delete ip2group[ctx.ip];
|
|
72
72
|
});
|
|
73
73
|
ctx.state.originalStream = body;
|
|
74
|
+
body.on('error', e => ts.destroy(e)); // pipe doesn't forward source errors, so make Koa's response stream report them
|
|
74
75
|
ctx.body = body.pipe(ts);
|
|
75
76
|
if (downloadTotal !== undefined) // undefined will break SSE
|
|
76
77
|
ctx.response.length = downloadTotal; // preserve this info
|
package/src/update.js
CHANGED
|
@@ -28,8 +28,8 @@ const persistence_1 = require("./persistence");
|
|
|
28
28
|
const lodash_1 = __importDefault(require("lodash"));
|
|
29
29
|
const argv_1 = require("./argv");
|
|
30
30
|
const promises_2 = require("stream/promises");
|
|
31
|
-
const updateToBeta = (0, config_1.defineConfig)(
|
|
32
|
-
const autoCheckUpdate = (0, config_1.defineConfig)(
|
|
31
|
+
const updateToBeta = (0, config_1.defineConfig)(misc_1.CFG.update_to_beta, false);
|
|
32
|
+
const autoCheckUpdate = (0, config_1.defineConfig)(misc_1.CFG.auto_check_update, true);
|
|
33
33
|
const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0);
|
|
34
34
|
const AUTO_CHECK_EVERY = misc_1.DAY;
|
|
35
35
|
exports.autoCheckUpdateResult = persistence_1.storedMap.singleSync('autoCheckUpdateResult', undefined);
|
|
@@ -162,7 +162,13 @@ async function update(tagOrUrl = '') {
|
|
|
162
162
|
} while ((0, fs_1.existsSync)((0, path_1.join)(binPath, newBinFile)));
|
|
163
163
|
plugins_1.pluginsWatcher.pause();
|
|
164
164
|
try {
|
|
165
|
-
|
|
165
|
+
try {
|
|
166
|
+
await (0, misc_1.unzip)((0, fs_1.createReadStream)(LOCAL_UPDATE), path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path));
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
await (0, promises_1.rename)(LOCAL_UPDATE, 'hfs-update-bad.zip'); // quarantine failed archives so they don't take precedence over future downloads
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
166
172
|
const newBin = (0, path_1.join)(binPath, newBinFile);
|
|
167
173
|
if (!(0, fs_1.existsSync)(newBin)) {
|
|
168
174
|
if (url) // the file was downloaded, and the UI would show the "update from local file" button until we remove it
|
package/src/upload.js
CHANGED
|
@@ -23,10 +23,15 @@ const events_1 = __importDefault(require("./events"));
|
|
|
23
23
|
const promises_1 = require("fs/promises");
|
|
24
24
|
const expiringCache_1 = require("./expiringCache");
|
|
25
25
|
const first_1 = require("./first");
|
|
26
|
-
|
|
27
|
-
exports.
|
|
28
|
-
exports.
|
|
26
|
+
const uploadOwners_1 = require("./uploadOwners");
|
|
27
|
+
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)(misc_1.CFG.delete_unfinished_uploads_after, 86_400);
|
|
28
|
+
exports.minAvailableMb = (0, config_1.defineConfig)(misc_1.CFG.min_available_mb, 100);
|
|
29
|
+
exports.dontOverwriteUploading = (0, config_1.defineConfig)(misc_1.CFG.dont_overwrite_uploading, true);
|
|
29
30
|
const waitingToBeDeleted = {};
|
|
31
|
+
function cancelDeletion(path) {
|
|
32
|
+
clearTimeout(waitingToBeDeleted[path]?.timeout);
|
|
33
|
+
delete waitingToBeDeleted[path];
|
|
34
|
+
}
|
|
30
35
|
(0, first_1.onProcessExit)(() => {
|
|
31
36
|
if (!Object.keys(waitingToBeDeleted).length)
|
|
32
37
|
return;
|
|
@@ -48,7 +53,7 @@ function setUploadMeta(path, ctx) {
|
|
|
48
53
|
});
|
|
49
54
|
}
|
|
50
55
|
function getUploadTempFor(fullPath) {
|
|
51
|
-
return (0, path_1.join)((0, path_1.dirname)(fullPath),
|
|
56
|
+
return (0, path_1.join)((0, path_1.dirname)(fullPath), const_1.UPLOAD_TEMP_PREFIX + (0, path_1.basename)(fullPath).slice(-200));
|
|
52
57
|
}
|
|
53
58
|
const diskSpaceCache = (0, expiringCache_1.expiringCache)(3_000); // invalidate shortly
|
|
54
59
|
const uploadingFiles = new Map();
|
|
@@ -105,6 +110,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
105
110
|
setUploadMeta(dir, ctx);
|
|
106
111
|
// use temporary name while uploading
|
|
107
112
|
const tempName = getUploadTempFor(fullPath);
|
|
113
|
+
const tempUri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(tempName));
|
|
108
114
|
// try to catch errors early (sync): this is avoiding on chrome139 when uploading a big file (1GB) to get miss the error code and have to make a `simulate`
|
|
109
115
|
try {
|
|
110
116
|
fs_1.default.accessSync(tempName, fs_1.default.constants.W_OK);
|
|
@@ -130,8 +136,11 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
130
136
|
return fail(const_1.HTTP_PRECONDITION_FAILED);
|
|
131
137
|
}
|
|
132
138
|
// append if resuming
|
|
133
|
-
if (!resume && stats)
|
|
139
|
+
if (!resume && stats) {
|
|
140
|
+
// a new upload discards the old resumable temp, so its owner grant must not survive
|
|
141
|
+
(0, uploadOwners_1.deleteUploadOwner)(tempUri);
|
|
134
142
|
fs_1.default.unlinkSync(tempName);
|
|
143
|
+
}
|
|
135
144
|
const writeStream = (0, misc_1.createStreamLimiter)(isNaN(contentLength) ? Infinity : contentLength);
|
|
136
145
|
const fullSize = stillToWrite + resume;
|
|
137
146
|
// allow plugins to mess with the write-stream, because the read-stream can be complicated in case of multipart
|
|
@@ -152,6 +161,9 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
152
161
|
const tracked = { ctx, got: 0, size: stillToWrite };
|
|
153
162
|
uploadingFiles.set(fullPath, tracked);
|
|
154
163
|
console.debug('Upload started');
|
|
164
|
+
let tempOwnerSet = false;
|
|
165
|
+
// ownership must be visible before a follow-up request can act on the unfinished file
|
|
166
|
+
ctx.req.once('aborted', setTempOwner);
|
|
155
167
|
// the file stream doesn't have an event for data being written, so we use 'data' of its feeder, which happens before, so we postpone a bit, trying to have a fresher number
|
|
156
168
|
writeStream.on('data', () => setTimeout(() => tracked.got = bytesGot()));
|
|
157
169
|
const lockMiddleware = (0, misc_1.pendingPromise)(); // expose outside, to let know when all operations stopped
|
|
@@ -171,7 +183,8 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
171
183
|
return (0, promises_1.rm)(tempName).catch(() => { });
|
|
172
184
|
if (ctx.isAborted()) { // in the very unlikely case the connection is interrupted between last-byte and here, we still consider it unfinished, as the client had no way to know, and will resume, but it would get an error if we finish the process
|
|
173
185
|
const sec = exports.deleteUnfinishedUploadsAfter.get();
|
|
174
|
-
|
|
186
|
+
setTempOwner();
|
|
187
|
+
return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec, tempUri);
|
|
175
188
|
}
|
|
176
189
|
if (isPartial) // we are supposed to leave the unfinished upload as it is, with its temp name
|
|
177
190
|
return ctx.status = const_1.HTTP_NO_CONTENT; // lockMiddleware contains an empty string, so we must take care of the status
|
|
@@ -194,6 +207,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
194
207
|
() => (0, promises_1.rename)(tempName, dest).then(() => true, e => e?.code !== 'EBUSY' && Promise.reject(e)), { timeout: 10_000 });
|
|
195
208
|
if (!done)
|
|
196
209
|
throw 'EBUSY';
|
|
210
|
+
(0, uploadOwners_1.deleteUploadOwner)(tempUri); // the temp URI no longer exists after rename; final ownership is recorded below
|
|
197
211
|
if (mtime) // so we use it to touch the file
|
|
198
212
|
await (0, promises_1.utimes)(dest, Date.now() / 1000, mtime / 1000);
|
|
199
213
|
cancelDeletion(tempName); // not necessary, as deletion's failure is silent, but still
|
|
@@ -202,6 +216,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
202
216
|
if (ctx.query.comment)
|
|
203
217
|
void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
|
|
204
218
|
obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
|
|
219
|
+
await (0, uploadOwners_1.setUploadOwner)(obj.uri, ctx);
|
|
205
220
|
events_1.default.emit('uploadFinished', obj);
|
|
206
221
|
console.debug("Upload finished", dest);
|
|
207
222
|
if (resEvent)
|
|
@@ -245,6 +260,12 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
245
260
|
function bytesGot() {
|
|
246
261
|
return fileStream.bytesWritten + fileStream.writableLength;
|
|
247
262
|
}
|
|
263
|
+
function setTempOwner() {
|
|
264
|
+
if (tempOwnerSet)
|
|
265
|
+
return;
|
|
266
|
+
tempOwnerSet = true;
|
|
267
|
+
void (0, uploadOwners_1.setUploadOwner)(tempUri, ctx);
|
|
268
|
+
}
|
|
248
269
|
}
|
|
249
270
|
catch (e) {
|
|
250
271
|
releaseFile();
|
|
@@ -259,21 +280,18 @@ function uploadWriter(base, baseUri, filename, ctx) {
|
|
|
259
280
|
overwriteRequestedButForbidden = true;
|
|
260
281
|
return false;
|
|
261
282
|
}
|
|
262
|
-
function delayedDelete(path, secs) {
|
|
283
|
+
function delayedDelete(path, secs, tempUri) {
|
|
263
284
|
clearTimeout(waitingToBeDeleted[path]?.timeout);
|
|
264
285
|
return waitingToBeDeleted[path] = {
|
|
265
286
|
mtime,
|
|
266
287
|
expires: Date.now() + secs * 1000,
|
|
267
288
|
timeout: setTimeout(() => {
|
|
268
289
|
delete waitingToBeDeleted[path];
|
|
290
|
+
(0, uploadOwners_1.deleteUploadOwner)(tempUri);
|
|
269
291
|
(0, promises_1.rm)(path).catch(() => { });
|
|
270
292
|
}, secs * 1000)
|
|
271
293
|
};
|
|
272
294
|
}
|
|
273
|
-
function cancelDeletion(path) {
|
|
274
|
-
clearTimeout(waitingToBeDeleted[path]?.timeout);
|
|
275
|
-
delete waitingToBeDeleted[path];
|
|
276
|
-
}
|
|
277
295
|
function releaseFile() {
|
|
278
296
|
uploadingFiles.delete(fullPath);
|
|
279
297
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadOwners = exports.ownUploadDeleteHours = void 0;
|
|
7
|
+
exports.setUploadOwner = setUploadOwner;
|
|
8
|
+
exports.moveUploadOwner = moveUploadOwner;
|
|
9
|
+
exports.deleteUploadOwner = deleteUploadOwner;
|
|
10
|
+
exports.getSessionId = getSessionId;
|
|
11
|
+
const kvstorage_1 = require("@rejetto/kvstorage");
|
|
12
|
+
const misc_1 = require("./misc");
|
|
13
|
+
const first_1 = require("./first");
|
|
14
|
+
const config_1 = require("./config");
|
|
15
|
+
const auth_1 = require("./auth");
|
|
16
|
+
const events_1 = __importDefault(require("./events"));
|
|
17
|
+
exports.ownUploadDeleteHours = (0, config_1.defineConfig)(misc_1.CFG.own_upload_delete_hours, 24);
|
|
18
|
+
exports.uploadOwners = new kvstorage_1.KvStorage({
|
|
19
|
+
rewriteLater: true,
|
|
20
|
+
});
|
|
21
|
+
exports.uploadOwners.open('upload-owners.kv').catch(e => console.error("Upload owners won't work correctly", e));
|
|
22
|
+
(0, first_1.onProcessExit)(() => exports.uploadOwners.close());
|
|
23
|
+
setInterval(() => {
|
|
24
|
+
if (!exports.uploadOwners.isOpen())
|
|
25
|
+
return;
|
|
26
|
+
const keys = Array.from(exports.uploadOwners.keys());
|
|
27
|
+
for (const k of keys) {
|
|
28
|
+
const owner = exports.uploadOwners.getSync(k);
|
|
29
|
+
if (owner && isExpired(owner))
|
|
30
|
+
void exports.uploadOwners.del(k);
|
|
31
|
+
}
|
|
32
|
+
}, misc_1.MINUTE);
|
|
33
|
+
events_1.default.on('checkVfsPermission', ({ node, perm, ctx }) => {
|
|
34
|
+
if (perm !== 'can_delete' || !node.source)
|
|
35
|
+
return;
|
|
36
|
+
const { vfsPath } = node;
|
|
37
|
+
if (!vfsPath || !exports.ownUploadDeleteHours.get() || !exports.uploadOwners.isOpen())
|
|
38
|
+
return;
|
|
39
|
+
const owner = exports.uploadOwners.getSync(cleanVfsPath(vfsPath));
|
|
40
|
+
if (!owner)
|
|
41
|
+
return;
|
|
42
|
+
if (isExpired(owner)) {
|
|
43
|
+
deleteUploadOwner(vfsPath);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const username = (0, auth_1.getCurrentUsername)(ctx);
|
|
47
|
+
const { sessionId } = owner;
|
|
48
|
+
if (sessionId && sessionId === ctx.session?.sessionId || username && owner.username === username)
|
|
49
|
+
return 0;
|
|
50
|
+
});
|
|
51
|
+
function setUploadOwner(vfsPath, ctx) {
|
|
52
|
+
if (!exports.uploadOwners.isOpen() || !exports.ownUploadDeleteHours.get())
|
|
53
|
+
return;
|
|
54
|
+
const username = (0, auth_1.getCurrentUsername)(ctx) || undefined;
|
|
55
|
+
return exports.uploadOwners.put(cleanVfsPath(vfsPath), {
|
|
56
|
+
username,
|
|
57
|
+
sessionId: username ? undefined : getSessionId(ctx),
|
|
58
|
+
ip: ctx.ip,
|
|
59
|
+
created: new Date(),
|
|
60
|
+
})?.catch(e => {
|
|
61
|
+
console.error("Couldn't store upload owner for", vfsPath, String(e.message || e));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function moveUploadOwner(fromPath, toPath) {
|
|
65
|
+
if (!exports.uploadOwners.isOpen())
|
|
66
|
+
return;
|
|
67
|
+
const from = cleanVfsPath(fromPath);
|
|
68
|
+
const affected = Array.from(exports.uploadOwners.keys()).filter(k => isSameOrInside(from, k));
|
|
69
|
+
if (!affected.length)
|
|
70
|
+
return;
|
|
71
|
+
const to = cleanVfsPath(toPath);
|
|
72
|
+
const owners = affected.map(k => ({ k, owner: exports.uploadOwners.getSync(k) }));
|
|
73
|
+
// ownership is keyed by VFS path, so HFS moves must carry descendant upload records too
|
|
74
|
+
await Promise.all(owners.map(({ k, owner }) => owner && exports.uploadOwners.put(to + k.slice(from.length), owner)));
|
|
75
|
+
await Promise.all(affected.map(k => exports.uploadOwners.del(k)));
|
|
76
|
+
}
|
|
77
|
+
function deleteUploadOwner(vfsPath) {
|
|
78
|
+
if (exports.uploadOwners.isOpen())
|
|
79
|
+
// deleting a folder must clear ownership for uploaded files below it too
|
|
80
|
+
for (const k of Array.from(exports.uploadOwners.keys()).filter(k => isSameOrInside(cleanVfsPath(vfsPath), k)))
|
|
81
|
+
void exports.uploadOwners.del(k);
|
|
82
|
+
}
|
|
83
|
+
function isExpired(owner) {
|
|
84
|
+
const hours = exports.ownUploadDeleteHours.get();
|
|
85
|
+
return !hours || Number(owner.created) + hours * misc_1.HOUR <= Date.now();
|
|
86
|
+
}
|
|
87
|
+
function getSessionId(ctx) {
|
|
88
|
+
return ctx.session.sessionId ||= (0, misc_1.randomId)(30);
|
|
89
|
+
}
|
|
90
|
+
function cleanVfsPath(path) {
|
|
91
|
+
return '/' + path.replace(/^\/+|\/+$/g, '');
|
|
92
|
+
}
|
|
93
|
+
function isSameOrInside(parent, path) {
|
|
94
|
+
return path === parent || path.startsWith(parent + '/');
|
|
95
|
+
}
|
package/src/util-files.js
CHANGED
|
@@ -31,7 +31,7 @@ const const_1 = require("./const");
|
|
|
31
31
|
const promises_2 = require("stream/promises");
|
|
32
32
|
const stat_1 = require("./stat");
|
|
33
33
|
const unzipper_1 = __importDefault(require("unzipper"));
|
|
34
|
-
const fileTimeout = (0, config_1.defineConfig)(
|
|
34
|
+
const fileTimeout = (0, config_1.defineConfig)(cross_1.CFG.file_timeout, 3, x => x * 1000);
|
|
35
35
|
// a smart (and a bit arbitrary) way to decide if we need the stat-workers functionality. Without it, we may be a bit faster. We'll see with experience if we need a dedicated configuration.
|
|
36
36
|
const disableStatWorkers = Number(process.env.UV_THREADPOOL_SIZE) >= 10;
|
|
37
37
|
// some paths (mostly offline networked ones) can take 20+ seconds to fail
|
|
@@ -106,7 +106,9 @@ function escapeGlobPath(path) {
|
|
|
106
106
|
async function unzip(stream, cb) {
|
|
107
107
|
const extracted = new Map();
|
|
108
108
|
let chain = Promise.resolve();
|
|
109
|
-
|
|
109
|
+
const parser = unzipper_1.default.Parse();
|
|
110
|
+
stream.once('error', e => parser.destroy(e)); // pipe doesn't forward source errors, so make the parser reject and release its resources
|
|
111
|
+
return new Promise((resolve, reject) => stream.pipe(parser)
|
|
110
112
|
.on('close', () => chain.then(resolve, reject))
|
|
111
113
|
.on('error', reject)
|
|
112
114
|
.on('entry', (entry) => chain = chain.then(async () => {
|
package/src/util-http.js
CHANGED
|
@@ -64,6 +64,7 @@ async function httpWithBody(url, options) {
|
|
|
64
64
|
function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...options } = {}, redirected = []) {
|
|
65
65
|
const controller = new AbortController();
|
|
66
66
|
options.signal ??= controller.signal;
|
|
67
|
+
options.localAddress ??= httpStream.defaultLocalAddress;
|
|
67
68
|
return Object.assign(new Promise(async (resolve, reject) => {
|
|
68
69
|
proxy ??= httpStream.defaultProxy;
|
|
69
70
|
options.headers ??= {};
|
|
@@ -140,7 +141,7 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
|
|
|
140
141
|
if (options.timeout) // node only emits the timeout event, so destroy the request to unblock callers waiting for the body
|
|
141
142
|
req.setTimeout(options.timeout, () => req.destroy(Object.assign(Error('timeout'), { code: 'ETIMEDOUT' })));
|
|
142
143
|
if (body && body instanceof node_stream_1.Readable)
|
|
143
|
-
body.
|
|
144
|
+
body.on('error', e => req.destroy(e)).pipe(req);
|
|
144
145
|
else
|
|
145
146
|
req.end(body);
|
|
146
147
|
function connect() {
|
|
@@ -149,6 +150,7 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
|
|
|
149
150
|
(proxyParsed.protocol === 'https:' ? node_https_1.default : node_http_1.default).request({
|
|
150
151
|
...proxyParsed,
|
|
151
152
|
auth: undefined, // void proxyParsed.auth
|
|
153
|
+
localAddress: options.localAddress, // the proxy socket is the actual outbound connection when tunneling HTTPS
|
|
152
154
|
method: 'CONNECT',
|
|
153
155
|
path,
|
|
154
156
|
headers: { Host: path, ...proxyAuth }
|
package/src/vfs.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.saveVfs = exports.vfs = void 0;
|
|
|
8
8
|
exports.permsFromParent = permsFromParent;
|
|
9
9
|
exports.isSameFilenameAs = isSameFilenameAs;
|
|
10
10
|
exports.normalizeFilename = normalizeFilename;
|
|
11
|
+
exports.getFreeVfsName = getFreeVfsName;
|
|
11
12
|
exports.applyParentToChild = applyParentToChild;
|
|
12
13
|
exports.urlToNode = urlToNode;
|
|
13
14
|
exports.nodeStats = nodeStats;
|
|
@@ -37,7 +38,12 @@ const comments_1 = require("./comments");
|
|
|
37
38
|
const walkDir_1 = require("./walkDir");
|
|
38
39
|
const node_stream_1 = require("node:stream");
|
|
39
40
|
const adminApis_1 = require("./adminApis");
|
|
40
|
-
const showHiddenFiles = (0, config_1.defineConfig)(
|
|
41
|
+
const showHiddenFiles = (0, config_1.defineConfig)(misc_1.CFG.show_hidden_files, false);
|
|
42
|
+
function setVfsPath(node, vfsPath, parent) {
|
|
43
|
+
return (0, misc_1.setHidden)(node, {
|
|
44
|
+
vfsPath: (0, misc_1.enforceFinal)('/', parent?.vfsPath) + (0, misc_1.pathEncode)(vfsPath)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
41
47
|
function permsFromParent(parent, child) {
|
|
42
48
|
const ret = {};
|
|
43
49
|
for (const k of misc_1.PERM_KEYS) {
|
|
@@ -80,21 +86,29 @@ function isSameFilenameAs(name) {
|
|
|
80
86
|
function normalizeFilename(x) {
|
|
81
87
|
return (const_1.IS_WINDOWS || const_1.IS_MAC ? x.toLocaleLowerCase() : x).normalize();
|
|
82
88
|
}
|
|
89
|
+
function getFreeVfsName(siblings, name) {
|
|
90
|
+
const ext = (0, path_1.extname)(name);
|
|
91
|
+
const noExt = ext ? name.slice(0, -ext.length) : name;
|
|
92
|
+
let idx = 2;
|
|
93
|
+
while (siblings?.find(isSameFilenameAs(name)))
|
|
94
|
+
name = `${noExt} ${idx++}${ext}`;
|
|
95
|
+
return name;
|
|
96
|
+
}
|
|
83
97
|
async function applyParentToChild(child, parent, name) {
|
|
84
|
-
|
|
98
|
+
name ||= child ? getNodeName(child) : '';
|
|
99
|
+
const ret = setVfsPath({
|
|
85
100
|
original: child, // this can be overridden by passing an 'original' in `child`
|
|
86
101
|
...child,
|
|
87
102
|
isFolder: child?.isFolder ?? (child?.children?.length > 0 || undefined), // isFolder is hidden in original node, so we must copy it explicitly
|
|
88
103
|
isTemp: true,
|
|
89
104
|
parent,
|
|
90
|
-
};
|
|
91
|
-
name ||= child ? getNodeName(child) : '';
|
|
105
|
+
}, name, parent);
|
|
92
106
|
inheritMasks(ret, parent, name);
|
|
93
107
|
await parentMaskApplier(parent)(ret, name);
|
|
94
108
|
inheritFromParent(ret);
|
|
95
109
|
return ret;
|
|
96
110
|
}
|
|
97
|
-
async function urlToNode(url, ctx, parent = exports.vfs, allowMissing // true means missing path segments still resolve to temporary nodes with a computed source path
|
|
111
|
+
async function urlToNode(url, ctx, parent = exports.vfs.compiled(), allowMissing // true means missing path segments still resolve to temporary nodes with a computed source path
|
|
98
112
|
) {
|
|
99
113
|
let initialSlashes = 0;
|
|
100
114
|
while (url[initialSlashes] === '/')
|
|
@@ -112,6 +126,7 @@ async function urlToNode(url, ctx, parent = exports.vfs, allowMissing // true me
|
|
|
112
126
|
const ret = await getNodeByName(name, parent, assumeFolder);
|
|
113
127
|
if (!ret)
|
|
114
128
|
return;
|
|
129
|
+
setVfsPath(ret, name, parent);
|
|
115
130
|
if (rest || ret?.original)
|
|
116
131
|
return urlToNode(rest, ctx, ret, allowMissing);
|
|
117
132
|
if (ret.source)
|
|
@@ -164,36 +179,49 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
164
179
|
return ret;
|
|
165
180
|
}
|
|
166
181
|
}
|
|
167
|
-
const smartUncFolderDetection = (0, config_1.defineConfig)(
|
|
182
|
+
const smartUncFolderDetection = (0, config_1.defineConfig)(misc_1.CFG.smart_unc_folder_detection, false);
|
|
168
183
|
async function setIsFolder(node) {
|
|
169
184
|
if (!node.source)
|
|
170
185
|
return;
|
|
171
|
-
const isFolder =
|
|
186
|
+
const isFolder = (0, misc_1.hasFinalSlash)(node.source)
|
|
172
187
|
|| smartUncFolderDetection.get() && (0, misc_1.getUncHost)(node.source) && !(0, path_1.basename)(node.source).includes('.') // no dot = folder; not very reliable but fast for unreachable unc hosts, and it's an opt-in
|
|
173
188
|
|| await nodeStats(node).then(x => x?.isDirectory(), () => undefined);
|
|
174
189
|
(0, misc_1.setHidden)(node, { isFolder });
|
|
190
|
+
if (isFolder)
|
|
191
|
+
persistFolderMarker(node);
|
|
175
192
|
return isFolder;
|
|
176
193
|
}
|
|
177
|
-
|
|
178
|
-
(0, config_1.defineConfig)(
|
|
179
|
-
|
|
194
|
+
// compiled is the stable mutable tree, while get() may return a fresh clone of the default
|
|
195
|
+
exports.vfs = (0, config_1.defineConfig)(misc_1.CFG.vfs, {}, x => setVfsPath(structuredClone(x && typeof x === 'object' ? x : {}), '')); // ensure the type is right
|
|
196
|
+
exports.vfs.sub(async () => {
|
|
197
|
+
await reviewVfs();
|
|
180
198
|
console.log('VFS ready');
|
|
181
199
|
});
|
|
182
|
-
async function reviewVfs(
|
|
200
|
+
async function reviewVfs() {
|
|
183
201
|
await (async function recur(node) {
|
|
184
202
|
if (node.source && !node.children?.length && node.isFolder === undefined)
|
|
185
203
|
await setIsFolder(node);
|
|
186
|
-
if (node.children)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
204
|
+
if (!node.children)
|
|
205
|
+
return;
|
|
206
|
+
// we rename your node in case you got 2 nodes with the same name
|
|
207
|
+
const usedNames = new Set();
|
|
208
|
+
for (const child of node.children) {
|
|
209
|
+
const name = getNodeName(child);
|
|
210
|
+
const normalized = normalizeFilename(name);
|
|
211
|
+
if (usedNames.has(normalized))
|
|
212
|
+
child.name = getFreeVfsName(node.children, name);
|
|
213
|
+
usedNames.add(normalizeFilename(getNodeName(child)));
|
|
214
|
+
}
|
|
215
|
+
await Promise.allSettled(node.children.map(recur));
|
|
216
|
+
})(exports.vfs.compiled());
|
|
190
217
|
}
|
|
191
218
|
exports.saveVfs = (0, misc_1.debounceAsync)(async () => {
|
|
192
|
-
await reviewVfs();
|
|
193
|
-
|
|
219
|
+
await reviewVfs(); // refresh runtime-derived folder flags before saving mutated VFS state
|
|
220
|
+
exports.vfs.set(exports.vfs.compiled()); // sync the mutable runtime tree back to config state before persisting it
|
|
221
|
+
(0, config_1.saveConfigAsap)();
|
|
194
222
|
});
|
|
195
223
|
function isRoot(node) {
|
|
196
|
-
return node === exports.vfs;
|
|
224
|
+
return node === exports.vfs.compiled();
|
|
197
225
|
}
|
|
198
226
|
function getNodeName(node) {
|
|
199
227
|
if (isRoot(node))
|
|
@@ -221,12 +249,24 @@ function nodeIsFolder(node) {
|
|
|
221
249
|
function reconsider() {
|
|
222
250
|
// a networked source may be offline at startup, and become online later: recalculate in the background
|
|
223
251
|
nodeStats(node).then(s => {
|
|
224
|
-
if (s)
|
|
225
|
-
|
|
252
|
+
if (s) {
|
|
253
|
+
const isFolder = s.isDirectory();
|
|
254
|
+
(0, misc_1.setHidden)(node.original || node, { isFolder });
|
|
255
|
+
if (isFolder)
|
|
256
|
+
persistFolderMarker(node);
|
|
257
|
+
}
|
|
226
258
|
}, () => { });
|
|
227
259
|
return undefined;
|
|
228
260
|
}
|
|
229
261
|
}
|
|
262
|
+
// we mark folder paths with a final slash – the UI already does so, but the config may be modified
|
|
263
|
+
function persistFolderMarker(node) {
|
|
264
|
+
const stored = node.original || node; // temporary VFS nodes must update their stored original for saveVfs to persist the marker
|
|
265
|
+
if (!stored.source || (0, misc_1.hasFinalSlash)(stored.source))
|
|
266
|
+
return;
|
|
267
|
+
stored.source += path_1.sep;
|
|
268
|
+
void (0, exports.saveVfs)();
|
|
269
|
+
}
|
|
230
270
|
async function getDefaultFile(node, ctx) {
|
|
231
271
|
return node.default && nodeIsFolder(node) && await urlToNode(node.default, ctx, node) || undefined;
|
|
232
272
|
}
|
|
@@ -266,12 +306,9 @@ function statusCodeForMissingPerm(node, perm, ctx, assign = true) {
|
|
|
266
306
|
} while (1);
|
|
267
307
|
if ((0, misc_1.isWhoObject)(who) || isWhoVfsPerms(who))
|
|
268
308
|
throw Error(`permission type-guard: ${JSON.stringify(who)}`);
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
if (first !== undefined)
|
|
273
|
-
return first;
|
|
274
|
-
}
|
|
309
|
+
const first = lodash_1.default.max(events_1.default.emit('checkVfsPermission', { who, node, perm, ctx }));
|
|
310
|
+
if (first !== undefined)
|
|
311
|
+
return first;
|
|
275
312
|
return simpleWhoToError(who, ctx)
|
|
276
313
|
?? (0, misc_1.throw_)(Error(`invalid permission: ${perm}=${(0, misc_1.try_)(() => JSON.stringify(who))}`));
|
|
277
314
|
}
|
|
@@ -306,10 +343,10 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
306
343
|
const nodeName = getNodeName(child);
|
|
307
344
|
const name = prefixPath + nodeName;
|
|
308
345
|
taken?.add(normalizeFilename(name));
|
|
309
|
-
const item = { ...child, original: child, name, parent };
|
|
346
|
+
const item = setVfsPath({ ...child, original: child, name, parent }, name, parent);
|
|
310
347
|
if (await cantSee(item))
|
|
311
348
|
return;
|
|
312
|
-
if (item.source && !item.children?.length) // real items must be accessible, unless
|
|
349
|
+
if (item.source && !item.children?.length && !item.see_without_probing) // real items must be accessible, unless probing was explicitly disabled
|
|
313
350
|
try {
|
|
314
351
|
await promises_1.default.access(item.source);
|
|
315
352
|
}
|
|
@@ -349,7 +386,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
|
|
|
349
386
|
const name = prefixPath + (renamed || path);
|
|
350
387
|
if (taken?.has(normalizeFilename(name))) // taken by vfs node above
|
|
351
388
|
return false; // false just in case it's a folder
|
|
352
|
-
const item = { name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats };
|
|
389
|
+
const item = setVfsPath({ name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats }, name, parent);
|
|
353
390
|
// masks containing '/' must be matched against the relative path while keeping walkDir recursion enabled
|
|
354
391
|
await pathMaskApplier(item, renamed || path);
|
|
355
392
|
if (await cantSee(item)) // can't see: don't produce and don't recur
|
|
@@ -482,7 +519,7 @@ events_1.default.on('accountRenamed', ({ from, to }) => {
|
|
|
482
519
|
if (n.masks)
|
|
483
520
|
Object.values(n.masks).forEach(renameInNode);
|
|
484
521
|
n.children?.forEach(renameInNode);
|
|
485
|
-
})(exports.vfs);
|
|
522
|
+
})(exports.vfs.compiled());
|
|
486
523
|
(0, exports.saveVfs)();
|
|
487
524
|
function renameInPerm(a) {
|
|
488
525
|
if ((0, misc_1.isWhoObject)(a)) {
|
package/src/walkDir.js
CHANGED
|
@@ -23,12 +23,12 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
23
23
|
return new Promise(async (resolve, reject) => {
|
|
24
24
|
if (!await (0, util_files_1.isDirectory)(path))
|
|
25
25
|
return reject(Error('ENOTDIR'));
|
|
26
|
-
dirQ.add(() => readDir('', depth)
|
|
26
|
+
dirQ.add(() => readDir('', depth, [path])
|
|
27
27
|
.then(res => {
|
|
28
28
|
Promise.resolve(res?.branchDone).then(resolve);
|
|
29
29
|
}, reject));
|
|
30
30
|
});
|
|
31
|
-
async function readDir(relativePath, depth) {
|
|
31
|
+
async function readDir(relativePath, depth, ancestorPaths) {
|
|
32
32
|
if (stopped)
|
|
33
33
|
return;
|
|
34
34
|
const base = (0, path_1.join)(path, relativePath);
|
|
@@ -39,6 +39,8 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
39
39
|
const pluginReceiver = lodash_1.default.isFunction(res) && res || null;
|
|
40
40
|
const pluginIterator = lodash_1.default.isFunction(res?.[Symbol.asyncIterator] || res?.[Symbol.iterator]) && res;
|
|
41
41
|
if (const_1.IS_WINDOWS && !pluginIterator) { // use native apis to read the 'hidden' attribute
|
|
42
|
+
// fswin callbacks cannot await, so track their work before closing the branch
|
|
43
|
+
const entriesWorking = [];
|
|
42
44
|
const direntMethods = {
|
|
43
45
|
isDir: false,
|
|
44
46
|
isFile() { return !this.isDir; },
|
|
@@ -46,14 +48,16 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
46
48
|
isBlockDevice() { return false; },
|
|
47
49
|
isCharacterDevice() { return false; },
|
|
48
50
|
};
|
|
49
|
-
await new Promise(
|
|
50
|
-
if (event !== 'FOUND')
|
|
51
|
-
|
|
51
|
+
await new Promise((resolve, reject) => fswin_1.default.find(base + '\\*', (event, f) => {
|
|
52
|
+
if (event !== 'FOUND') {
|
|
53
|
+
Promise.all(entriesWorking).then(() => resolve());
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
52
56
|
if (stopped)
|
|
53
57
|
return true; // stop signal
|
|
54
58
|
if (!hidden && f.IS_HIDDEN)
|
|
55
59
|
return;
|
|
56
|
-
work(Object.assign(Object.create(direntMethods), {
|
|
60
|
+
entriesWorking.push(work(Object.assign(Object.create(direntMethods), {
|
|
57
61
|
isDir: f.IS_DIRECTORY,
|
|
58
62
|
name: f.LONG_NAME,
|
|
59
63
|
stats: {
|
|
@@ -63,7 +67,7 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
63
67
|
isFile: () => !f.IS_DIRECTORY,
|
|
64
68
|
isDirectory: () => f.IS_DIRECTORY,
|
|
65
69
|
}
|
|
66
|
-
}));
|
|
70
|
+
}), Boolean(f.REPARSE_POINT_TAG)).catch(reject));
|
|
67
71
|
}, true));
|
|
68
72
|
}
|
|
69
73
|
else
|
|
@@ -72,7 +76,9 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
72
76
|
break;
|
|
73
77
|
if (!hidden && !const_1.IS_WINDOWS && entry.name[0] === '.')
|
|
74
78
|
continue;
|
|
75
|
-
|
|
79
|
+
// stat follows links, so preserve their identity for cycle detection
|
|
80
|
+
const isSymlink = entry.isSymbolicLink?.();
|
|
81
|
+
const stats = isSymlink && await (0, util_files_1.statWithTimeout)((0, path_1.join)(base, entry.name)).catch(() => null);
|
|
76
82
|
if (stats === null)
|
|
77
83
|
continue;
|
|
78
84
|
if (stats)
|
|
@@ -80,7 +86,7 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
80
86
|
const expanded = entry;
|
|
81
87
|
if (stats)
|
|
82
88
|
expanded.stats = stats;
|
|
83
|
-
await work(expanded);
|
|
89
|
+
await work(expanded, isSymlink);
|
|
84
90
|
}
|
|
85
91
|
pluginReceiver?.(!stopped);
|
|
86
92
|
const branchDone = Promise.allSettled(subDirsDone);
|
|
@@ -90,7 +96,7 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
90
96
|
closingQ.push(relativePath); // ok, we'll ask next one to carry this info
|
|
91
97
|
// don't return the promise directly, as this job ends here, but communicate to caller the promise for the whole branch
|
|
92
98
|
return { branchDone, n };
|
|
93
|
-
async function work(entry) {
|
|
99
|
+
async function work(entry, isSymlink = false) {
|
|
94
100
|
entry.path = (relativePath && relativePath + '/') + entry.name;
|
|
95
101
|
pluginReceiver?.(entry);
|
|
96
102
|
if (last && closingQ.length) // pending entries
|
|
@@ -104,9 +110,16 @@ function walkDir(path, { depth = 0, hidden = true, parallelizeRecursion = false,
|
|
|
104
110
|
n++;
|
|
105
111
|
if (!depth || !entry.isDirectory())
|
|
106
112
|
return;
|
|
113
|
+
const entryPath = (0, path_1.join)(base, entry.name);
|
|
114
|
+
if (isSymlink) {
|
|
115
|
+
// compare only with ancestors so separate links to the same folder still work
|
|
116
|
+
const [target, ...ancestors] = await Promise.all([entryPath, ...ancestorPaths].map(x => (0, promises_1.realpath)(x).catch(() => '')));
|
|
117
|
+
if (!target || ancestors.includes(target))
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
107
120
|
const branchDone = (0, cross_1.pendingPromise)(); // per-job
|
|
108
121
|
subDirsDone.push(branchDone);
|
|
109
|
-
const job = () => readDir(entry.path, depth - 1) // recur
|
|
122
|
+
const job = () => readDir(entry.path, depth - 1, [...ancestorPaths, entryPath]) // recur
|
|
110
123
|
.then(x => x, () => { }) // mute errors
|
|
111
124
|
.then(res => {
|
|
112
125
|
if (!res?.n)
|
package/src/watchLoad.js
CHANGED
|
@@ -12,8 +12,9 @@ const debounceAsync_1 = require("./debounceAsync");
|
|
|
12
12
|
const events_1 = require("./events");
|
|
13
13
|
function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {}) {
|
|
14
14
|
let doing = false;
|
|
15
|
+
let cancelled = false;
|
|
15
16
|
let watcher;
|
|
16
|
-
const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000, reuseRunning: true });
|
|
17
|
+
const debounced = (0, debounceAsync_1.debounceAsync)(load, { wait: 500, maxWait: 1000, reuseRunning: true, cancelable: true });
|
|
17
18
|
let retry;
|
|
18
19
|
let last;
|
|
19
20
|
const emitter = new events_1.BetterEventEmitter();
|
|
@@ -27,6 +28,8 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
|
|
|
27
28
|
install(true);
|
|
28
29
|
return { unwatch, save, emitter, getText: () => last, getPath: () => path };
|
|
29
30
|
function install(first = false) {
|
|
31
|
+
if (cancelled)
|
|
32
|
+
return;
|
|
30
33
|
try {
|
|
31
34
|
watcher = (0, fs_1.watch)(path, () => {
|
|
32
35
|
if (!save.isWorking())
|
|
@@ -43,6 +46,12 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
function unwatch() {
|
|
49
|
+
// a pending or running load must not resurrect a watcher cancelled by its owner
|
|
50
|
+
cancelled = true;
|
|
51
|
+
debounced.cancel();
|
|
52
|
+
closeWatcher();
|
|
53
|
+
}
|
|
54
|
+
function closeWatcher() {
|
|
46
55
|
watcher?.close();
|
|
47
56
|
clearTimeout(retry);
|
|
48
57
|
watcher = undefined;
|
|
@@ -61,12 +70,14 @@ function watchLoad(path, parser, { failedOnFirstAttempt, immediateFirst } = {})
|
|
|
61
70
|
return '';
|
|
62
71
|
throw e;
|
|
63
72
|
});
|
|
73
|
+
if (cancelled)
|
|
74
|
+
return;
|
|
64
75
|
if (text === last)
|
|
65
76
|
return;
|
|
66
77
|
last = text;
|
|
67
78
|
emitter.emit('change', last);
|
|
68
79
|
console.debug('Loaded', path);
|
|
69
|
-
|
|
80
|
+
closeWatcher();
|
|
70
81
|
install(); // reinstall, as the original file could have been renamed. We watch by the name.
|
|
71
82
|
await parser(text);
|
|
72
83
|
}
|