copyparty 1.16.18__py3-none-any.whl → 1.16.19__py3-none-any.whl
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.
- copyparty/__init__.py +1 -1
- copyparty/__main__.py +20 -2
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +62 -9
- copyparty/cfg.py +3 -0
- copyparty/httpcli.py +38 -18
- copyparty/ico.py +13 -2
- copyparty/pwhash.py +1 -1
- copyparty/svchub.py +148 -61
- copyparty/th_cli.py +23 -4
- copyparty/th_srv.py +16 -3
- copyparty/u2idx.py +2 -2
- copyparty/up2k.py +7 -4
- copyparty/util.py +81 -3
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/deps/marked.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- {copyparty-1.16.18.dist-info → copyparty-1.16.19.dist-info}/METADATA +15 -3
- {copyparty-1.16.18.dist-info → copyparty-1.16.19.dist-info}/RECORD +24 -24
- {copyparty-1.16.18.dist-info → copyparty-1.16.19.dist-info}/WHEEL +1 -1
- {copyparty-1.16.18.dist-info → copyparty-1.16.19.dist-info}/entry_points.txt +0 -0
- {copyparty-1.16.18.dist-info → copyparty-1.16.19.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.16.18.dist-info → copyparty-1.16.19.dist-info}/top_level.txt +0 -0
copyparty/__init__.py
CHANGED
copyparty/__main__.py
CHANGED
@@ -222,7 +222,23 @@ def init_E(EE ) :
|
|
222
222
|
if E.mod.endswith("__init__"):
|
223
223
|
E.mod = os.path.dirname(E.mod)
|
224
224
|
|
225
|
-
|
225
|
+
try:
|
226
|
+
p = os.environ.get("XDG_CONFIG_HOME")
|
227
|
+
if not p:
|
228
|
+
raise Exception()
|
229
|
+
if p.startswith("~"):
|
230
|
+
p = os.path.expanduser(p)
|
231
|
+
p = os.path.abspath(os.path.realpath(p))
|
232
|
+
p = os.path.join(p, "copyparty")
|
233
|
+
if not os.path.isdir(p):
|
234
|
+
os.mkdir(p)
|
235
|
+
os.listdir(p)
|
236
|
+
except:
|
237
|
+
p = ""
|
238
|
+
|
239
|
+
if p:
|
240
|
+
E.cfg = p
|
241
|
+
elif sys.platform == "win32":
|
226
242
|
bdir = os.environ.get("APPDATA") or os.environ.get("TEMP") or "."
|
227
243
|
E.cfg = os.path.normpath(bdir + "/copyparty")
|
228
244
|
elif sys.platform == "darwin":
|
@@ -1003,7 +1019,7 @@ def add_upload(ap):
|
|
1003
1019
|
ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
|
1004
1020
|
ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
|
1005
1021
|
ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
|
1006
|
-
ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
|
1022
|
+
ap2.add_argument("--u2ow", metavar="NUM", type=int, default=0, help="web-client: default setting for when to replace/overwrite existing files; [\033[32m0\033[0m]=never, [\033[32m1\033[0m]=if-client-newer, [\033[32m2\033[0m]=always (volflag=u2ow)")
|
1007
1023
|
ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
|
1008
1024
|
ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
|
1009
1025
|
|
@@ -1385,6 +1401,7 @@ def add_db_general(ap, hcores):
|
|
1385
1401
|
ap2.add_argument("-e2vu", action="store_true", help="on hash mismatch: update the database with the new hash")
|
1386
1402
|
ap2.add_argument("-e2vp", action="store_true", help="on hash mismatch: panic and quit copyparty")
|
1387
1403
|
ap2.add_argument("--hist", metavar="PATH", type=u, default="", help="where to store volume data (db, thumbs); default is a folder named \".hist\" inside each volume (volflag=hist)")
|
1404
|
+
ap2.add_argument("--dbpath", metavar="PATH", type=u, default="", help="override where the volume databases are to be placed; default is the same as \033[33m--hist\033[0m (volflag=dbpath)")
|
1388
1405
|
ap2.add_argument("--no-hash", metavar="PTN", type=u, default="", help="regex: disable hashing of matching absolute-filesystem-paths during e2ds folder scans (volflag=nohash)")
|
1389
1406
|
ap2.add_argument("--no-idx", metavar="PTN", type=u, default=noidx, help="regex: disable indexing of matching absolute-filesystem-paths during e2ds folder scans (volflag=noidx)")
|
1390
1407
|
ap2.add_argument("--no-dirsz", action="store_true", help="do not show total recursive size of folders in listings, show inode size instead; slightly faster (volflag=nodirsz)")
|
@@ -1423,6 +1440,7 @@ def add_db_metadata(ap):
|
|
1423
1440
|
|
1424
1441
|
def add_txt(ap):
|
1425
1442
|
ap2 = ap.add_argument_group('textfile options')
|
1443
|
+
ap2.add_argument("--md-hist", metavar="TXT", type=u, default="s", help="where to store old version of markdown files; [\033[32ms\033[0m]=subfolder, [\033[32mv\033[0m]=volume-histpath, [\033[32mn\033[0m]=nope/disabled (volflag=md_hist)")
|
1426
1444
|
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="the textfile editor will check for serverside changes every \033[33mSEC\033[0m seconds")
|
1427
1445
|
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins -- neat but dangerous, big XSS risk")
|
1428
1446
|
ap2.add_argument("--exp", action="store_true", help="enable textfile expansion -- replace {{self.ip}} and such; see \033[33m--help-exp\033[0m (volflag=exp)")
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
@@ -353,6 +353,7 @@ class VFS(object):
|
|
353
353
|
self.badcfg1 = False
|
354
354
|
self.nodes = {} # child nodes
|
355
355
|
self.histtab = {} # all realpath->histpath
|
356
|
+
self.dbpaths = {} # all realpath->dbpath
|
356
357
|
self.dbv = None # closest full/non-jump parent
|
357
358
|
self.lim = None # upload limits; only set for dbv
|
358
359
|
self.shr_src = None # source vfs+rem of a share
|
@@ -374,12 +375,13 @@ class VFS(object):
|
|
374
375
|
rp = realpath + ("" if realpath.endswith(os.sep) else os.sep)
|
375
376
|
vp = vpath + ("/" if vpath else "")
|
376
377
|
self.histpath = os.path.join(realpath, ".hist") # db / thumbcache
|
378
|
+
self.dbpath = self.histpath
|
377
379
|
self.all_vols = {vpath: self} # flattened recursive
|
378
380
|
self.all_nodes = {vpath: self} # also jumpvols/shares
|
379
381
|
self.all_aps = [(rp, self)]
|
380
382
|
self.all_vps = [(vp, self)]
|
381
383
|
else:
|
382
|
-
self.histpath = ""
|
384
|
+
self.histpath = self.dbpath = ""
|
383
385
|
self.all_vols = {}
|
384
386
|
self.all_nodes = {}
|
385
387
|
self.all_aps = []
|
@@ -454,17 +456,23 @@ class VFS(object):
|
|
454
456
|
|
455
457
|
def _copy_flags(self, name ) :
|
456
458
|
flags = {k: v for k, v in self.flags.items()}
|
459
|
+
|
457
460
|
hist = flags.get("hist")
|
458
461
|
if hist and hist != "-":
|
459
462
|
zs = "{}/{}".format(hist.rstrip("/"), name)
|
460
463
|
flags["hist"] = os.path.expandvars(os.path.expanduser(zs))
|
461
464
|
|
465
|
+
dbp = flags.get("dbpath")
|
466
|
+
if dbp and dbp != "-":
|
467
|
+
zs = "{}/{}".format(dbp.rstrip("/"), name)
|
468
|
+
flags["dbpath"] = os.path.expandvars(os.path.expanduser(zs))
|
469
|
+
|
462
470
|
return flags
|
463
471
|
|
464
472
|
def bubble_flags(self) :
|
465
473
|
if self.dbv:
|
466
474
|
for k, v in self.dbv.flags.items():
|
467
|
-
if k not in
|
475
|
+
if k not in ("hist", "dbpath"):
|
468
476
|
self.flags[k] = v
|
469
477
|
|
470
478
|
for n in self.nodes.values():
|
@@ -1752,7 +1760,7 @@ class AuthSrv(object):
|
|
1752
1760
|
pass
|
1753
1761
|
elif vflag:
|
1754
1762
|
vflag = os.path.expandvars(os.path.expanduser(vflag))
|
1755
|
-
vol.histpath = uncyg(vflag) if WINDOWS else vflag
|
1763
|
+
vol.histpath = vol.dbpath = uncyg(vflag) if WINDOWS else vflag
|
1756
1764
|
elif self.args.hist:
|
1757
1765
|
for nch in range(len(hid)):
|
1758
1766
|
hpath = os.path.join(self.args.hist, hid[: nch + 1])
|
@@ -1773,12 +1781,45 @@ class AuthSrv(object):
|
|
1773
1781
|
with open(powner, "wb") as f:
|
1774
1782
|
f.write(me)
|
1775
1783
|
|
1776
|
-
vol.histpath = hpath
|
1784
|
+
vol.histpath = vol.dbpath = hpath
|
1777
1785
|
break
|
1778
1786
|
|
1779
1787
|
vol.histpath = absreal(vol.histpath)
|
1788
|
+
|
1789
|
+
for vol in vfs.all_vols.values():
|
1790
|
+
hid = self.hid_cache[vol.realpath]
|
1791
|
+
vflag = vol.flags.get("dbpath")
|
1792
|
+
if vflag == "-":
|
1793
|
+
pass
|
1794
|
+
elif vflag:
|
1795
|
+
vflag = os.path.expandvars(os.path.expanduser(vflag))
|
1796
|
+
vol.dbpath = uncyg(vflag) if WINDOWS else vflag
|
1797
|
+
elif self.args.dbpath:
|
1798
|
+
for nch in range(len(hid)):
|
1799
|
+
hpath = os.path.join(self.args.dbpath, hid[: nch + 1])
|
1800
|
+
bos.makedirs(hpath)
|
1801
|
+
|
1802
|
+
powner = os.path.join(hpath, "owner.txt")
|
1803
|
+
try:
|
1804
|
+
with open(powner, "rb") as f:
|
1805
|
+
owner = f.read().rstrip()
|
1806
|
+
except:
|
1807
|
+
owner = None
|
1808
|
+
|
1809
|
+
me = afsenc(vol.realpath).rstrip()
|
1810
|
+
if owner not in [None, me]:
|
1811
|
+
continue
|
1812
|
+
|
1813
|
+
if owner is None:
|
1814
|
+
with open(powner, "wb") as f:
|
1815
|
+
f.write(me)
|
1816
|
+
|
1817
|
+
vol.dbpath = hpath
|
1818
|
+
break
|
1819
|
+
|
1820
|
+
vol.dbpath = absreal(vol.dbpath)
|
1780
1821
|
if vol.dbv:
|
1781
|
-
if bos.path.exists(os.path.join(vol.
|
1822
|
+
if bos.path.exists(os.path.join(vol.dbpath, "up2k.db")):
|
1782
1823
|
promote.append(vol)
|
1783
1824
|
vol.dbv = None
|
1784
1825
|
else:
|
@@ -1793,9 +1834,7 @@ class AuthSrv(object):
|
|
1793
1834
|
"\n the following jump-volumes were generated to assist the vfs.\n As they contain a database (probably from v0.11.11 or older),\n they are promoted to full volumes:"
|
1794
1835
|
]
|
1795
1836
|
for vol in promote:
|
1796
|
-
ta.append(
|
1797
|
-
" /{} ({}) ({})".format(vol.vpath, vol.realpath, vol.histpath)
|
1798
|
-
)
|
1837
|
+
ta.append(" /%s (%s) (%s)" % (vol.vpath, vol.realpath, vol.dbpath))
|
1799
1838
|
|
1800
1839
|
self.log("\n\n".join(ta) + "\n", c=3)
|
1801
1840
|
|
@@ -1806,13 +1845,27 @@ class AuthSrv(object):
|
|
1806
1845
|
is_shr = shr and zv.vpath.split("/")[0] == shr
|
1807
1846
|
if histp and not is_shr and histp in rhisttab:
|
1808
1847
|
zv2 = rhisttab[histp]
|
1809
|
-
t = "invalid config; multiple volumes share the same histpath (database location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
|
1848
|
+
t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
|
1810
1849
|
t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
1811
1850
|
self.log(t, 1)
|
1812
1851
|
raise Exception(t)
|
1813
1852
|
rhisttab[histp] = zv
|
1814
1853
|
vfs.histtab[zv.realpath] = histp
|
1815
1854
|
|
1855
|
+
rdbpaths = {}
|
1856
|
+
vfs.dbpaths = {}
|
1857
|
+
for zv in vfs.all_vols.values():
|
1858
|
+
dbp = zv.dbpath
|
1859
|
+
is_shr = shr and zv.vpath.split("/")[0] == shr
|
1860
|
+
if dbp and not is_shr and dbp in rdbpaths:
|
1861
|
+
zv2 = rdbpaths[dbp]
|
1862
|
+
t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: %s [%s]"
|
1863
|
+
t = t % (dbp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
1864
|
+
self.log(t, 1)
|
1865
|
+
raise Exception(t)
|
1866
|
+
rdbpaths[dbp] = zv
|
1867
|
+
vfs.dbpaths[zv.realpath] = dbp
|
1868
|
+
|
1816
1869
|
for vol in vfs.all_vols.values():
|
1817
1870
|
use = False
|
1818
1871
|
for k in ["zipmaxn", "zipmaxs"]:
|
copyparty/cfg.py
CHANGED
@@ -83,6 +83,7 @@ def vf_vmap() :
|
|
83
83
|
"md_sbf",
|
84
84
|
"lg_sba",
|
85
85
|
"md_sba",
|
86
|
+
"md_hist",
|
86
87
|
"nrand",
|
87
88
|
"u2ow",
|
88
89
|
"og_desc",
|
@@ -204,6 +205,7 @@ flagcats = {
|
|
204
205
|
"d2v": "disables file verification, overrides -e2v*",
|
205
206
|
"d2d": "disables all database stuff, overrides -e2*",
|
206
207
|
"hist=/tmp/cdb": "puts thumbnails and indexes at that location",
|
208
|
+
"dbpath=/tmp/cdb": "puts indexes at that location",
|
207
209
|
"scan=60": "scan for new files every 60sec, same as --re-maxage",
|
208
210
|
"nohash=\\.iso$": "skips hashing file contents if path matches *.iso",
|
209
211
|
"noidx=\\.iso$": "fully ignores the contents at paths matching *.iso",
|
@@ -291,6 +293,7 @@ flagcats = {
|
|
291
293
|
"og_ua": "if defined: only send OG html if useragent matches this regex",
|
292
294
|
},
|
293
295
|
"textfiles": {
|
296
|
+
"md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
|
294
297
|
"exp": "enable textfile expansion; see --help-exp",
|
295
298
|
"exp_md": "placeholders to expand in markdown files; see --help",
|
296
299
|
"exp_lg": "placeholders to expand in prologue/epilogue; see --help",
|
copyparty/httpcli.py
CHANGED
@@ -57,6 +57,7 @@ from .util import (
|
|
57
57
|
UnrecvEOF,
|
58
58
|
WrongPostKey,
|
59
59
|
absreal,
|
60
|
+
afsenc,
|
60
61
|
alltrace,
|
61
62
|
atomic_move,
|
62
63
|
b64dec,
|
@@ -2983,9 +2984,6 @@ class HttpCli(object):
|
|
2983
2984
|
vfs, rem = self.asrv.vfs.get(vpath, self.uname, False, True)
|
2984
2985
|
rem = sanitize_vpath(rem, "/")
|
2985
2986
|
fn = vfs.canonical(rem)
|
2986
|
-
if not fn.startswith(vfs.realpath):
|
2987
|
-
self.log("invalid mkdir %r %r" % (self.gctx, vpath), 1)
|
2988
|
-
raise Pebkac(422)
|
2989
2987
|
|
2990
2988
|
if not nullwrite:
|
2991
2989
|
fdir = os.path.dirname(fn)
|
@@ -3484,6 +3482,7 @@ class HttpCli(object):
|
|
3484
3482
|
|
3485
3483
|
fp = os.path.join(fp, fn)
|
3486
3484
|
rem = "{}/{}".format(rp, fn).strip("/")
|
3485
|
+
dbv, vrem = vfs.get_dbv(rem)
|
3487
3486
|
|
3488
3487
|
if not rem.endswith(".md") and not self.can_delete:
|
3489
3488
|
raise Pebkac(400, "only markdown pls")
|
@@ -3538,13 +3537,27 @@ class HttpCli(object):
|
|
3538
3537
|
mdir, mfile = os.path.split(fp)
|
3539
3538
|
fname, fext = mfile.rsplit(".", 1) if "." in mfile else (mfile, "md")
|
3540
3539
|
mfile2 = "{}.{:.3f}.{}".format(fname, srv_lastmod, fext)
|
3541
|
-
|
3540
|
+
|
3541
|
+
dp = ""
|
3542
|
+
hist_cfg = dbv.flags["md_hist"]
|
3543
|
+
if hist_cfg == "v":
|
3544
|
+
vrd = vsplit(vrem)[0]
|
3545
|
+
zb = hashlib.sha512(afsenc(vrd)).digest()
|
3546
|
+
zs = ub64enc(zb).decode("ascii")[:24].lower()
|
3547
|
+
dp = "%s/md/%s/%s/%s" % (dbv.histpath, zs[:2], zs[2:4], zs)
|
3548
|
+
self.log("moving old version to %s/%s" % (dp, mfile2))
|
3549
|
+
if bos.makedirs(dp):
|
3550
|
+
with open(os.path.join(dp, "dir.txt"), "wb") as f:
|
3551
|
+
f.write(afsenc(vrd))
|
3552
|
+
elif hist_cfg == "s":
|
3542
3553
|
dp = os.path.join(mdir, ".hist")
|
3543
|
-
|
3544
|
-
|
3545
|
-
|
3546
|
-
|
3547
|
-
|
3554
|
+
try:
|
3555
|
+
bos.mkdir(dp)
|
3556
|
+
hidedir(dp)
|
3557
|
+
except:
|
3558
|
+
pass
|
3559
|
+
if dp:
|
3560
|
+
wrename(self.log, fp, os.path.join(dp, mfile2), vfs.flags)
|
3548
3561
|
|
3549
3562
|
p_field, _, p_data = next(self.parser.gen)
|
3550
3563
|
if p_field != "body":
|
@@ -3616,13 +3629,12 @@ class HttpCli(object):
|
|
3616
3629
|
wunlink(self.log, fp, vfs.flags)
|
3617
3630
|
raise Pebkac(403, t)
|
3618
3631
|
|
3619
|
-
vfs, rem = vfs.get_dbv(rem)
|
3620
3632
|
self.conn.hsrv.broker.say(
|
3621
3633
|
"up2k.hash_file",
|
3622
|
-
|
3623
|
-
|
3624
|
-
|
3625
|
-
vsplit(
|
3634
|
+
dbv.realpath,
|
3635
|
+
dbv.vpath,
|
3636
|
+
dbv.flags,
|
3637
|
+
vsplit(vrem)[0],
|
3626
3638
|
fn,
|
3627
3639
|
self.ip,
|
3628
3640
|
new_lastmod,
|
@@ -4849,7 +4861,7 @@ class HttpCli(object):
|
|
4849
4861
|
self.reply(pt.encode("utf-8"), status=rc)
|
4850
4862
|
return True
|
4851
4863
|
|
4852
|
-
if "th" in self.ouparam:
|
4864
|
+
if "th" in self.ouparam and str(self.ouparam["th"])[:1] in "jw":
|
4853
4865
|
return self.tx_svg("e" + pt[:3])
|
4854
4866
|
|
4855
4867
|
# most webdav clients will not send credentials until they
|
@@ -5776,7 +5788,13 @@ class HttpCli(object):
|
|
5776
5788
|
|
5777
5789
|
thp = None
|
5778
5790
|
if self.thumbcli and not nothumb:
|
5779
|
-
|
5791
|
+
try:
|
5792
|
+
thp = self.thumbcli.get(dbv, vrem, int(st.st_mtime), th_fmt)
|
5793
|
+
except Pebkac as ex:
|
5794
|
+
if ex.code == 500 and th_fmt[:1] in "jw":
|
5795
|
+
self.log("failed to convert [%s]:\n%s" % (abspath, ex), 3)
|
5796
|
+
return self.tx_svg("--error--\ncheck\nserver\nlog")
|
5797
|
+
raise
|
5780
5798
|
|
5781
5799
|
if thp:
|
5782
5800
|
return self.tx_file(thp)
|
@@ -5998,9 +6016,11 @@ class HttpCli(object):
|
|
5998
6016
|
# check for old versions of files,
|
5999
6017
|
# [num-backups, most-recent, hist-path]
|
6000
6018
|
hist = {}
|
6001
|
-
histdir = os.path.join(fsroot, ".hist")
|
6002
|
-
ptn = RE_MDV
|
6003
6019
|
try:
|
6020
|
+
if vf["md_hist"] != "s":
|
6021
|
+
raise Exception()
|
6022
|
+
histdir = os.path.join(fsroot, ".hist")
|
6023
|
+
ptn = RE_MDV
|
6004
6024
|
for hfn in bos.listdir(histdir):
|
6005
6025
|
m = ptn.match(hfn)
|
6006
6026
|
if not m:
|
copyparty/ico.py
CHANGED
@@ -94,10 +94,21 @@ class Ico(object):
|
|
94
94
|
<?xml version="1.0" encoding="UTF-8"?>
|
95
95
|
<svg version="1.1" viewBox="0 0 100 {}" xmlns="http://www.w3.org/2000/svg"><g>
|
96
96
|
<rect width="100%" height="100%" fill="#{}" />
|
97
|
-
<text x="50%" y="
|
97
|
+
<text x="50%" y="{}" dominant-baseline="middle" text-anchor="middle" xml:space="preserve"
|
98
98
|
fill="#{}" font-family="monospace" font-size="14px" style="letter-spacing:.5px">{}</text>
|
99
99
|
</g></svg>
|
100
100
|
"""
|
101
|
-
|
101
|
+
|
102
|
+
txt = html_escape(ext, True)
|
103
|
+
if "\n" in txt:
|
104
|
+
lines = txt.split("\n")
|
105
|
+
n = len(lines)
|
106
|
+
y = "20%" if n == 2 else "10%" if n == 3 else "0"
|
107
|
+
zs = '<tspan x="50%%" dy="1.2em">%s</tspan>'
|
108
|
+
txt = "".join([zs % (x,) for x in lines])
|
109
|
+
else:
|
110
|
+
y = "50%"
|
111
|
+
|
112
|
+
svg = svg.format(h, c[:6], y, c[6:], txt)
|
102
113
|
|
103
114
|
return "image/svg+xml", svg.encode("utf-8")
|
copyparty/pwhash.py
CHANGED
copyparty/svchub.py
CHANGED
@@ -58,6 +58,7 @@ from .util import (
|
|
58
58
|
expat_ver,
|
59
59
|
gzip,
|
60
60
|
load_ipu,
|
61
|
+
lock_file,
|
61
62
|
min_ex,
|
62
63
|
mp,
|
63
64
|
odfusion,
|
@@ -67,6 +68,9 @@ from .util import (
|
|
67
68
|
ub64enc,
|
68
69
|
)
|
69
70
|
|
71
|
+
if HAVE_SQLITE3:
|
72
|
+
import sqlite3
|
73
|
+
|
70
74
|
if TYPE_CHECKING:
|
71
75
|
try:
|
72
76
|
from .mdns import MDNS
|
@@ -78,6 +82,10 @@ if PY2:
|
|
78
82
|
range = xrange # type: ignore
|
79
83
|
|
80
84
|
|
85
|
+
VER_SESSION_DB = 1
|
86
|
+
VER_SHARES_DB = 2
|
87
|
+
|
88
|
+
|
81
89
|
class SvcHub(object):
|
82
90
|
"""
|
83
91
|
Hosts all services which cannot be parallelized due to reliance on monolithic resources.
|
@@ -180,8 +188,14 @@ class SvcHub(object):
|
|
180
188
|
|
181
189
|
if not args.use_fpool and args.j != 1:
|
182
190
|
args.no_fpool = True
|
183
|
-
t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems"
|
184
|
-
|
191
|
+
t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems, and make some antivirus-softwares "
|
192
|
+
c = 0
|
193
|
+
if ANYWIN:
|
194
|
+
t += "(especially Microsoft Defender) stress your CPU and HDD severely during big uploads"
|
195
|
+
c = 3
|
196
|
+
else:
|
197
|
+
t += "consume more resources (CPU/HDD) than normal"
|
198
|
+
self.log("root", t.format(args.j), c)
|
185
199
|
|
186
200
|
if not args.no_fpool and args.j != 1:
|
187
201
|
t = "WARNING: ignoring --use-fpool because multithreading (-j{}) is enabled"
|
@@ -400,25 +414,48 @@ class SvcHub(object):
|
|
400
414
|
self.log("root", t, 3)
|
401
415
|
return
|
402
416
|
|
403
|
-
import sqlite3
|
404
417
|
|
405
|
-
|
418
|
+
# policy:
|
419
|
+
# the sessions-db is whatever, if something looks broken then just nuke it
|
420
|
+
|
406
421
|
db_path = self.args.ses_db
|
407
|
-
|
408
|
-
|
422
|
+
db_lock = db_path + ".lock"
|
423
|
+
try:
|
424
|
+
create = not os.path.getsize(db_path)
|
425
|
+
except:
|
426
|
+
create = True
|
427
|
+
zs = "creating new" if create else "opening"
|
428
|
+
self.log("root", "%s sessions-db %s" % (zs, db_path))
|
429
|
+
|
430
|
+
for tries in range(2):
|
431
|
+
sver = 0
|
409
432
|
try:
|
410
433
|
db = sqlite3.connect(db_path)
|
411
434
|
cur = db.cursor()
|
412
435
|
try:
|
436
|
+
zs = "select v from kv where k='sver'"
|
437
|
+
sver = cur.execute(zs).fetchall()[0][0]
|
438
|
+
if sver > VER_SESSION_DB:
|
439
|
+
zs = "this version of copyparty only understands session-db v%d and older; the db is v%d"
|
440
|
+
raise Exception(zs % (VER_SESSION_DB, sver))
|
441
|
+
|
413
442
|
cur.execute("select count(*) from us").fetchone()
|
414
|
-
create = False
|
415
|
-
break
|
416
443
|
except:
|
417
|
-
|
444
|
+
if sver:
|
445
|
+
raise
|
446
|
+
sver = 1
|
447
|
+
self._create_session_db(cur)
|
448
|
+
err = self._verify_session_db(cur, sver, db_path)
|
449
|
+
if err:
|
450
|
+
tries = 99
|
451
|
+
self.args.no_ses = True
|
452
|
+
self.log("root", err, 3)
|
453
|
+
break
|
454
|
+
|
418
455
|
except Exception as ex:
|
419
|
-
if
|
456
|
+
if tries or sver > VER_SESSION_DB:
|
420
457
|
raise
|
421
|
-
t = "sessions-db
|
458
|
+
t = "sessions-db is unusable; deleting and recreating: %r"
|
422
459
|
self.log("root", t % (ex,), 3)
|
423
460
|
try:
|
424
461
|
cur.close() # type: ignore
|
@@ -428,8 +465,13 @@ class SvcHub(object):
|
|
428
465
|
db.close() # type: ignore
|
429
466
|
except:
|
430
467
|
pass
|
468
|
+
try:
|
469
|
+
os.unlink(db_lock)
|
470
|
+
except:
|
471
|
+
pass
|
431
472
|
os.unlink(db_path)
|
432
473
|
|
474
|
+
def _create_session_db(self, cur ) :
|
433
475
|
sch = [
|
434
476
|
r"create table kv (k text, v int)",
|
435
477
|
r"create table us (un text, si text, t0 int)",
|
@@ -439,15 +481,44 @@ class SvcHub(object):
|
|
439
481
|
r"create index us_t0 on us(t0)",
|
440
482
|
r"insert into kv values ('sver', 1)",
|
441
483
|
]
|
484
|
+
for cmd in sch:
|
485
|
+
cur.execute(cmd)
|
486
|
+
self.log("root", "created new sessions-db")
|
442
487
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
488
|
+
def _verify_session_db(self, cur , sver , db_path ) :
|
489
|
+
# ensure writable (maybe owned by other user)
|
490
|
+
db = cur.connection
|
491
|
+
|
492
|
+
try:
|
493
|
+
zil = cur.execute("select v from kv where k='pid'").fetchall()
|
494
|
+
if len(zil) > 1:
|
495
|
+
raise Exception()
|
496
|
+
owner = zil[0][0]
|
497
|
+
except:
|
498
|
+
owner = 0
|
499
|
+
|
500
|
+
if not lock_file(db_path + ".lock"):
|
501
|
+
t = "the sessions-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --ses-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now disable sessions and instead use plaintext passwords in cookies."
|
502
|
+
return t % (db_path, owner)
|
503
|
+
|
504
|
+
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
|
505
|
+
if owner:
|
506
|
+
# wear-estimate: 2 cells; offsets 0x10, 0x50, 0x19720
|
507
|
+
for k, v in vars:
|
508
|
+
cur.execute("update kv set v=? where k=?", (v, k))
|
509
|
+
else:
|
510
|
+
# wear-estimate: 3~4 cells; offsets 0x10, 0x50, 0x19180, 0x19710, 0x36000, 0x360b0, 0x36b90
|
511
|
+
for k, v in vars:
|
512
|
+
cur.execute("insert into kv values(?, ?)", (k, v))
|
448
513
|
|
514
|
+
if sver < VER_SESSION_DB:
|
515
|
+
cur.execute("delete from kv where k='sver'")
|
516
|
+
cur.execute("insert into kv values('sver',?)", (VER_SESSION_DB,))
|
517
|
+
|
518
|
+
db.commit()
|
449
519
|
cur.close()
|
450
520
|
db.close()
|
521
|
+
return ""
|
451
522
|
|
452
523
|
def setup_share_db(self) :
|
453
524
|
al = self.args
|
@@ -456,7 +527,6 @@ class SvcHub(object):
|
|
456
527
|
al.shr = ""
|
457
528
|
return
|
458
529
|
|
459
|
-
import sqlite3
|
460
530
|
|
461
531
|
al.shr = al.shr.strip("/")
|
462
532
|
if "/" in al.shr or not al.shr:
|
@@ -467,34 +537,48 @@ class SvcHub(object):
|
|
467
537
|
al.shr = "/%s/" % (al.shr,)
|
468
538
|
al.shr1 = al.shr[1:]
|
469
539
|
|
470
|
-
|
471
|
-
|
540
|
+
# policy:
|
541
|
+
# the shares-db is important, so panic if something is wrong
|
542
|
+
|
472
543
|
db_path = self.args.shr_db
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
544
|
+
db_lock = db_path + ".lock"
|
545
|
+
try:
|
546
|
+
create = not os.path.getsize(db_path)
|
547
|
+
except:
|
548
|
+
create = True
|
549
|
+
zs = "creating new" if create else "opening"
|
550
|
+
self.log("root", "%s shares-db %s" % (zs, db_path))
|
551
|
+
|
552
|
+
sver = 0
|
553
|
+
try:
|
554
|
+
db = sqlite3.connect(db_path)
|
555
|
+
cur = db.cursor()
|
556
|
+
if not create:
|
557
|
+
zs = "select v from kv where k='sver'"
|
558
|
+
sver = cur.execute(zs).fetchall()[0][0]
|
559
|
+
if sver > VER_SHARES_DB:
|
560
|
+
zs = "this version of copyparty only understands shares-db v%d and older; the db is v%d"
|
561
|
+
raise Exception(zs % (VER_SHARES_DB, sver))
|
562
|
+
|
563
|
+
cur.execute("select count(*) from sh").fetchone()
|
564
|
+
except Exception as ex:
|
565
|
+
t = "could not open shares-db; will now panic...\nthe following database must be repaired or deleted before you can launch copyparty:\n%s\n\nERROR: %s\n\nadditional details:\n%s\n"
|
566
|
+
self.log("root", t % (db_path, ex, min_ex()), 1)
|
567
|
+
raise
|
568
|
+
|
569
|
+
try:
|
570
|
+
zil = cur.execute("select v from kv where k='pid'").fetchall()
|
571
|
+
if len(zil) > 1:
|
572
|
+
raise Exception()
|
573
|
+
owner = zil[0][0]
|
574
|
+
except:
|
575
|
+
owner = 0
|
576
|
+
|
577
|
+
if not lock_file(db_lock):
|
578
|
+
t = "the shares-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --shr-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now panic."
|
579
|
+
t = t % (db_path, owner)
|
580
|
+
self.log("root", t, 1)
|
581
|
+
raise Exception(t)
|
498
582
|
|
499
583
|
sch1 = [
|
500
584
|
r"create table kv (k text, v int)",
|
@@ -506,32 +590,35 @@ class SvcHub(object):
|
|
506
590
|
r"create index sf_k on sf(k)",
|
507
591
|
r"create index sh_k on sh(k)",
|
508
592
|
r"create index sh_t1 on sh(t1)",
|
593
|
+
r"insert into kv values ('sver', 2)",
|
509
594
|
]
|
510
595
|
|
511
|
-
if
|
512
|
-
|
513
|
-
modified = True
|
596
|
+
if not sver:
|
597
|
+
sver = VER_SHARES_DB
|
514
598
|
for cmd in sch1 + sch2:
|
515
599
|
cur.execute(cmd)
|
516
600
|
self.log("root", "created new shares-db")
|
517
|
-
else:
|
518
|
-
(dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
|
519
601
|
|
520
|
-
if
|
521
|
-
modified = True
|
602
|
+
if sver == 1:
|
522
603
|
for cmd in sch2:
|
523
604
|
cur.execute(cmd)
|
524
605
|
cur.execute("update sh set st = 0")
|
525
606
|
self.log("root", "shares-db schema upgrade ok")
|
526
607
|
|
527
|
-
if
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
db
|
608
|
+
if sver < VER_SHARES_DB:
|
609
|
+
cur.execute("delete from kv where k='sver'")
|
610
|
+
cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
|
611
|
+
|
612
|
+
vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
|
613
|
+
if owner:
|
614
|
+
# wear-estimate: same as sessions-db
|
615
|
+
for k, v in vars:
|
616
|
+
cur.execute("update kv set v=? where k=?", (v, k))
|
617
|
+
else:
|
618
|
+
for k, v in vars:
|
619
|
+
cur.execute("insert into kv values(?, ?)", (k, v))
|
534
620
|
|
621
|
+
db.commit()
|
535
622
|
cur.close()
|
536
623
|
db.close()
|
537
624
|
|
@@ -669,8 +756,8 @@ class SvcHub(object):
|
|
669
756
|
t += ", "
|
670
757
|
t += "\033[0mNG: \033[35m" + sng
|
671
758
|
|
672
|
-
t += "\033[0m, see --deps"
|
673
|
-
self.log("dependencies", t, 6)
|
759
|
+
t += "\033[0m, see --deps (this is fine btw)"
|
760
|
+
self.log("optional-dependencies", t, 6)
|
674
761
|
|
675
762
|
def _check_env(self) :
|
676
763
|
try:
|
@@ -753,7 +840,7 @@ class SvcHub(object):
|
|
753
840
|
vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl]
|
754
841
|
setattr(al, k, vl)
|
755
842
|
|
756
|
-
for k in "lo hist ssl_log".split(" "):
|
843
|
+
for k in "lo hist dbpath ssl_log".split(" "):
|
757
844
|
vs = getattr(al, k)
|
758
845
|
if vs:
|
759
846
|
vs = os.path.expandvars(os.path.expanduser(vs))
|
copyparty/th_cli.py
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
+
import errno
|
4
5
|
import os
|
6
|
+
import stat
|
5
7
|
|
6
8
|
from .__init__ import TYPE_CHECKING
|
7
9
|
from .authsrv import VFS
|
8
10
|
from .bos import bos
|
9
11
|
from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path
|
10
|
-
from .util import Cooldown
|
12
|
+
from .util import Cooldown, Pebkac
|
11
13
|
|
12
14
|
if TYPE_CHECKING:
|
13
15
|
from .httpsrv import HttpSrv
|
14
16
|
|
15
17
|
|
18
|
+
IOERROR = "reading the file was denied by the server os; either due to filesystem permissions, selinux, apparmor, or similar:\n%r"
|
19
|
+
|
20
|
+
|
16
21
|
class ThumbCli(object):
|
17
22
|
def __init__(self, hsrv ) :
|
18
23
|
self.broker = hsrv.broker
|
@@ -121,7 +126,7 @@ class ThumbCli(object):
|
|
121
126
|
|
122
127
|
tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
|
123
128
|
tpaths = [tpath]
|
124
|
-
if fmt == "w":
|
129
|
+
if fmt[:1] == "w":
|
125
130
|
# also check for jpg (maybe webp is unavailable)
|
126
131
|
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
|
127
132
|
|
@@ -154,8 +159,22 @@ class ThumbCli(object):
|
|
154
159
|
if abort:
|
155
160
|
return None
|
156
161
|
|
157
|
-
|
158
|
-
|
162
|
+
ap = os.path.join(ptop, rem)
|
163
|
+
try:
|
164
|
+
st = bos.stat(ap)
|
165
|
+
if not st.st_size or not stat.S_ISREG(st.st_mode):
|
166
|
+
return None
|
167
|
+
|
168
|
+
with open(ap, "rb", 4) as f:
|
169
|
+
if not f.read(4):
|
170
|
+
raise Exception()
|
171
|
+
except OSError as ex:
|
172
|
+
if ex.errno == errno.ENOENT:
|
173
|
+
raise Pebkac(404)
|
174
|
+
else:
|
175
|
+
raise Pebkac(500, IOERROR % (ex,))
|
176
|
+
except Exception as ex:
|
177
|
+
raise Pebkac(500, IOERROR % (ex,))
|
159
178
|
|
160
179
|
x = self.broker.ask("thumbsrv.get", ptop, rem, mtime, fmt)
|
161
180
|
return x.get() # type: ignore
|
copyparty/th_srv.py
CHANGED
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
|
4
4
|
import hashlib
|
5
5
|
import logging
|
6
6
|
import os
|
7
|
+
import re
|
7
8
|
import shutil
|
8
9
|
import subprocess as sp
|
9
10
|
import threading
|
@@ -46,6 +47,9 @@ HAVE_WEBP = False
|
|
46
47
|
EXTS_TH = set(["jpg", "webp", "png"])
|
47
48
|
EXTS_AC = set(["opus", "owa", "caf", "mp3"])
|
48
49
|
|
50
|
+
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
|
51
|
+
|
52
|
+
|
49
53
|
try:
|
50
54
|
if os.environ.get("PRTY_NO_PIL"):
|
51
55
|
raise Exception()
|
@@ -382,8 +386,12 @@ class ThumbSrv(object):
|
|
382
386
|
self.log(msg, c)
|
383
387
|
if getattr(ex, "returncode", 0) != 321:
|
384
388
|
if fun == funs[-1]:
|
385
|
-
|
386
|
-
|
389
|
+
try:
|
390
|
+
with open(ttpath, "wb") as _:
|
391
|
+
pass
|
392
|
+
except Exception as ex:
|
393
|
+
t = "failed to create the file [%s]: %r"
|
394
|
+
self.log(t % (ttpath, ex), 3)
|
387
395
|
else:
|
388
396
|
# ffmpeg may spawn empty files on windows
|
389
397
|
try:
|
@@ -396,7 +404,10 @@ class ThumbSrv(object):
|
|
396
404
|
|
397
405
|
try:
|
398
406
|
wrename(self.log, ttpath, tpath, vn.flags)
|
399
|
-
except:
|
407
|
+
except Exception as ex:
|
408
|
+
if not os.path.exists(tpath):
|
409
|
+
t = "failed to move [%s] to [%s]: %r"
|
410
|
+
self.log(t % (ttpath, tpath, ex), 3)
|
400
411
|
pass
|
401
412
|
|
402
413
|
with self.mutex:
|
@@ -987,6 +998,8 @@ class ThumbSrv(object):
|
|
987
998
|
# thumb file
|
988
999
|
try:
|
989
1000
|
b64, ts, ext = f.split(".")
|
1001
|
+
if len(ts) > 8 and PTN_TS.match(ts):
|
1002
|
+
ts = "yeahokay"
|
990
1003
|
if len(b64) != 24 or len(ts) != 8 or ext not in exts:
|
991
1004
|
raise Exception()
|
992
1005
|
except:
|
copyparty/u2idx.py
CHANGED
@@ -128,9 +128,9 @@ class U2idx(object):
|
|
128
128
|
|
129
129
|
|
130
130
|
ptop = vn.realpath
|
131
|
-
histpath = self.asrv.vfs.
|
131
|
+
histpath = self.asrv.vfs.dbpaths.get(ptop)
|
132
132
|
if not histpath:
|
133
|
-
self.log("no
|
133
|
+
self.log("no dbpath for %r" % (ptop,))
|
134
134
|
return None
|
135
135
|
|
136
136
|
db_path = os.path.join(histpath, "up2k.db")
|
copyparty/up2k.py
CHANGED
@@ -91,7 +91,7 @@ VF_AFFECTS_INDEXING = set(zsg.split(" "))
|
|
91
91
|
|
92
92
|
SBUSY = "cannot receive uploads right now;\nserver busy with %s.\nPlease wait; the client will retry..."
|
93
93
|
|
94
|
-
HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
|
94
|
+
HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume), or, if you want to keep the thumbnails in the current location and only move the database itself, then use --dbpath or volflag dbpath"
|
95
95
|
|
96
96
|
|
97
97
|
NULLSTAT = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
|
@@ -1089,9 +1089,9 @@ class Up2k(object):
|
|
1089
1089
|
self, ptop , flags
|
1090
1090
|
) :
|
1091
1091
|
"""mutex(main,reg) me"""
|
1092
|
-
histpath = self.vfs.
|
1092
|
+
histpath = self.vfs.dbpaths.get(ptop)
|
1093
1093
|
if not histpath:
|
1094
|
-
self.log("no
|
1094
|
+
self.log("no dbpath for %r" % (ptop,))
|
1095
1095
|
return None
|
1096
1096
|
|
1097
1097
|
db_path = os.path.join(histpath, "up2k.db")
|
@@ -1336,12 +1336,15 @@ class Up2k(object):
|
|
1336
1336
|
]
|
1337
1337
|
excl += [absreal(x) for x in excl]
|
1338
1338
|
excl += list(self.vfs.histtab.values())
|
1339
|
+
excl += list(self.vfs.dbpaths.values())
|
1339
1340
|
if WINDOWS:
|
1340
1341
|
excl = [x.replace("/", "\\") for x in excl]
|
1341
1342
|
else:
|
1342
1343
|
# ~/.wine/dosdevices/z:/ and such
|
1343
1344
|
excl.extend(("/dev", "/proc", "/run", "/sys"))
|
1344
1345
|
|
1346
|
+
excl = list({k: 1 for k in excl})
|
1347
|
+
|
1345
1348
|
if self.args.re_dirsz:
|
1346
1349
|
db.c.execute("delete from ds")
|
1347
1350
|
db.n += 1
|
@@ -5078,7 +5081,7 @@ class Up2k(object):
|
|
5078
5081
|
|
5079
5082
|
def _snap_reg(self, ptop , reg ) :
|
5080
5083
|
now = time.time()
|
5081
|
-
histpath = self.vfs.
|
5084
|
+
histpath = self.vfs.dbpaths.get(ptop)
|
5082
5085
|
if not histpath:
|
5083
5086
|
return
|
5084
5087
|
|
copyparty/util.py
CHANGED
@@ -114,8 +114,14 @@ IP6ALL = "0:0:0:0:0:0:0:0"
|
|
114
114
|
|
115
115
|
|
116
116
|
try:
|
117
|
-
import ctypes
|
118
117
|
import fcntl
|
118
|
+
|
119
|
+
HAVE_FCNTL = True
|
120
|
+
except:
|
121
|
+
HAVE_FCNTL = False
|
122
|
+
|
123
|
+
try:
|
124
|
+
import ctypes
|
119
125
|
import termios
|
120
126
|
except:
|
121
127
|
pass
|
@@ -225,7 +231,7 @@ SYMTIME = PY36 and os.utime in os.supports_follow_symlinks
|
|
225
231
|
META_NOBOTS = '<meta name="robots" content="noindex, nofollow">\n'
|
226
232
|
|
227
233
|
# smart enough to understand javascript while also ignoring rel="nofollow"
|
228
|
-
BAD_BOTS = r"Barkrowler|bingbot|BLEXBot|Googlebot|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot"
|
234
|
+
BAD_BOTS = r"Barkrowler|bingbot|BLEXBot|Googlebot|GoogleOther|GPTBot|PetalBot|SeekportBot|SemrushBot|YandexBot"
|
229
235
|
|
230
236
|
FFMPEG_URL = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
|
231
237
|
|
@@ -1464,6 +1470,12 @@ def vol_san(vols , txt ) :
|
|
1464
1470
|
txt = txt.replace(bap.replace(b"\\", b"\\\\"), bvp)
|
1465
1471
|
txt = txt.replace(bhp.replace(b"\\", b"\\\\"), bvph)
|
1466
1472
|
|
1473
|
+
if vol.histpath != vol.dbpath:
|
1474
|
+
bdp = vol.dbpath.encode("utf-8")
|
1475
|
+
bdph = b"$db(/" + bvp + b")"
|
1476
|
+
txt = txt.replace(bdp, bdph)
|
1477
|
+
txt = txt.replace(bdp.replace(b"\\", b"\\\\"), bdph)
|
1478
|
+
|
1467
1479
|
if txt != txt0:
|
1468
1480
|
txt += b"\r\nNOTE: filepaths sanitized; see serverlog for correct values"
|
1469
1481
|
|
@@ -3843,8 +3855,74 @@ def hidedir(dp) :
|
|
3843
3855
|
pass
|
3844
3856
|
|
3845
3857
|
|
3858
|
+
_flocks = {}
|
3859
|
+
|
3860
|
+
|
3861
|
+
def _lock_file_noop(ap ) :
|
3862
|
+
return True
|
3863
|
+
|
3864
|
+
|
3865
|
+
def _lock_file_ioctl(ap ) :
|
3866
|
+
try:
|
3867
|
+
fd = _flocks.pop(ap)
|
3868
|
+
os.close(fd)
|
3869
|
+
except:
|
3870
|
+
pass
|
3871
|
+
|
3872
|
+
fd = os.open(ap, os.O_RDWR | os.O_CREAT, 438)
|
3873
|
+
# NOTE: the fcntl.lockf identifier is (pid,node);
|
3874
|
+
# the lock will be dropped if os.close(os.open(ap))
|
3875
|
+
# is performed anywhere else in this thread
|
3876
|
+
|
3877
|
+
try:
|
3878
|
+
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
3879
|
+
_flocks[ap] = fd
|
3880
|
+
return True
|
3881
|
+
except Exception as ex:
|
3882
|
+
eno = getattr(ex, "errno", -1)
|
3883
|
+
try:
|
3884
|
+
os.close(fd)
|
3885
|
+
except:
|
3886
|
+
pass
|
3887
|
+
if eno in (errno.EAGAIN, errno.EACCES):
|
3888
|
+
return False
|
3889
|
+
print("WARNING: unexpected errno %d from fcntl.lockf; %r" % (eno, ex))
|
3890
|
+
return True
|
3891
|
+
|
3892
|
+
|
3893
|
+
def _lock_file_windows(ap ) :
|
3894
|
+
try:
|
3895
|
+
import msvcrt
|
3896
|
+
|
3897
|
+
try:
|
3898
|
+
fd = _flocks.pop(ap)
|
3899
|
+
os.close(fd)
|
3900
|
+
except:
|
3901
|
+
pass
|
3902
|
+
|
3903
|
+
fd = os.open(ap, os.O_RDWR | os.O_CREAT, 438)
|
3904
|
+
msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
|
3905
|
+
return True
|
3906
|
+
except Exception as ex:
|
3907
|
+
eno = getattr(ex, "errno", -1)
|
3908
|
+
if eno == errno.EACCES:
|
3909
|
+
return False
|
3910
|
+
print("WARNING: unexpected errno %d from msvcrt.locking; %r" % (eno, ex))
|
3911
|
+
return True
|
3912
|
+
|
3913
|
+
|
3914
|
+
if os.environ.get("PRTY_NO_DB_LOCK"):
|
3915
|
+
lock_file = _lock_file_noop
|
3916
|
+
elif ANYWIN:
|
3917
|
+
lock_file = _lock_file_windows
|
3918
|
+
elif HAVE_FCNTL:
|
3919
|
+
lock_file = _lock_file_ioctl
|
3920
|
+
else:
|
3921
|
+
lock_file = _lock_file_noop
|
3922
|
+
|
3923
|
+
|
3846
3924
|
try:
|
3847
|
-
if sys.version_info < (3, 10):
|
3925
|
+
if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
|
3848
3926
|
# py3.8 doesn't have .files
|
3849
3927
|
# py3.9 has broken .is_file
|
3850
3928
|
raise ImportError()
|
copyparty/web/browser.css.gz
CHANGED
Binary file
|
copyparty/web/browser.js.gz
CHANGED
Binary file
|
copyparty/web/deps/marked.js.gz
CHANGED
Binary file
|
copyparty/web/ui.css.gz
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.16.
|
3
|
+
Version: 1.16.19
|
4
4
|
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
5
5
|
Author-email: ed <copyparty@ocv.me>
|
6
6
|
License: MIT
|
@@ -339,6 +339,8 @@ small collection of user feedback
|
|
339
339
|
|
340
340
|
`good enough`, `surprisingly correct`, `certified good software`, `just works`, `why`, `wow this is better than nextcloud`
|
341
341
|
|
342
|
+
* UI просто ужасно. Если буду описывать детально не смогу удержаться в рамках приличий
|
343
|
+
|
342
344
|
|
343
345
|
# motivations
|
344
346
|
|
@@ -387,7 +389,8 @@ roughly sorted by chance of encounter
|
|
387
389
|
* `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions (macos, some linux)
|
388
390
|
* `--th-ff-swr` may fix audio thumbnails on some FFmpeg versions
|
389
391
|
* if the `up2k.db` (filesystem index) is on a samba-share or network disk, you'll get unpredictable behavior if the share is disconnected for a bit
|
390
|
-
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db on a local disk instead
|
392
|
+
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db and thumbnails on a local disk instead
|
393
|
+
* or, if you only want to move the db (and not the thumbnails), then use `--dbpath` or the `dbpath` volflag
|
391
394
|
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
|
392
395
|
* probably more, pls let me know
|
393
396
|
|
@@ -440,7 +443,8 @@ same order here too
|
|
440
443
|
* this is an msys2 bug, the regular windows edition of python is fine
|
441
444
|
|
442
445
|
* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
|
443
|
-
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db inside the vm instead
|
446
|
+
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db and thumbnails inside the vm instead
|
447
|
+
* or, if you only want to move the db (and not the thumbnails), then use `--dbpath` or the `dbpath` volflag
|
444
448
|
* also happens on mergerfs, so put the db elsewhere
|
445
449
|
|
446
450
|
* Ubuntu: dragging files from certain folders into firefox or chrome is impossible
|
@@ -862,6 +866,8 @@ if you are resuming a massive upload and want to skip hashing the files which al
|
|
862
866
|
|
863
867
|
if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option `--u2sz` (the default is 96 MiB to support Cloudflare)
|
864
868
|
|
869
|
+
if you want to replace existing files on the server with new uploads by default, run with `--u2ow 2` (only works if users have the delete-permission, and can still be disabled with `🛡️` in the UI)
|
870
|
+
|
865
871
|
|
866
872
|
### file-search
|
867
873
|
|
@@ -1650,6 +1656,8 @@ copyparty creates a subfolder named `.hist` inside each volume where it stores t
|
|
1650
1656
|
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volflag, or a mix of both:
|
1651
1657
|
* `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
1652
1658
|
|
1659
|
+
by default, the per-volume `up2k.db` sqlite3-database for `-e2d` and `-e2t` is stored next to the thumbnails according to the `--hist` option, but the global-option `--dbpath` and/or volflag `dbpath` can be used to put the database somewhere else
|
1660
|
+
|
1653
1661
|
note:
|
1654
1662
|
* putting the hist-folders on an SSD is strongly recommended for performance
|
1655
1663
|
* markdown edits are always stored in a local `.hist` subdirectory
|
@@ -2185,7 +2193,9 @@ buggy feature? rip it out by setting any of the following environment variables
|
|
2185
2193
|
|
2186
2194
|
| env-var | what it does |
|
2187
2195
|
| -------------------- | ------------ |
|
2196
|
+
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
|
2188
2197
|
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
|
2198
|
+
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
|
2189
2199
|
| `PRTY_NO_IPV6` | disable some ipv6 support (should not be necessary since windows 2000) |
|
2190
2200
|
| `PRTY_NO_LZMA` | disable streaming xz compression of incoming uploads |
|
2191
2201
|
| `PRTY_NO_MP` | disable all use of the python `multiprocessing` module (actual multithreading, cpu-count for parsers/thumbnailers) |
|
@@ -2503,6 +2513,8 @@ below are some tweaks roughly ordered by usefulness:
|
|
2503
2513
|
* `--no-hash .` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
|
2504
2514
|
* if your volumes are on a network-disk such as NFS / SMB / s3, specifying larger values for `--iobuf` and/or `--s-rd-sz` and/or `--s-wr-sz` may help; try setting all of them to `524288` or `1048576` or `4194304`
|
2505
2515
|
* `--no-htp --hash-mt=0 --mtag-mt=1 --th-mt=1` minimizes the number of threads; can help in some eccentric environments (like the vscode debugger)
|
2516
|
+
* when running on AlpineLinux or other musl-based distro, try mimalloc for higher performance (and twice as much RAM usage); `apk add mimalloc2` and run copyparty with env-var `LD_PRELOAD=/usr/lib/libmimalloc-secure.so.2`
|
2517
|
+
* note that mimalloc requires special care when combined with prisonparty and/or bubbleparty/bubblewrap; you must give it access to `/proc` and `/sys` otherwise you'll encounter issues with FFmpeg (audio transcoding, thumbnails)
|
2506
2518
|
* `-j0` enables multiprocessing (actual multithreading), can reduce latency to `20+80/numCores` percent and generally improve performance in cpu-intensive workloads, for example:
|
2507
2519
|
* lots of connections (many users or heavy clients)
|
2508
2520
|
* simultaneous downloads and uploads saturating a 20gbps connection
|
@@ -1,38 +1,38 @@
|
|
1
|
-
copyparty/__init__.py,sha256=
|
2
|
-
copyparty/__main__.py,sha256=
|
3
|
-
copyparty/__version__.py,sha256=
|
4
|
-
copyparty/authsrv.py,sha256=
|
1
|
+
copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
|
2
|
+
copyparty/__main__.py,sha256=RuEnJf0a2d7ZbV2bOBj43iNkdBIG_zDUh0DbVUgsc50,119139
|
3
|
+
copyparty/__version__.py,sha256=M7ZuH7-ojW4ae8FrXsuVGdN6xOoyyx4SciZROZSQbUg,251
|
4
|
+
copyparty/authsrv.py,sha256=S4HrUq-C6wpe_IT3KN75yo9GJw9Uzj5y558ooYCQqng,113286
|
5
5
|
copyparty/broker_mp.py,sha256=QdOXXvV2Xn6J0CysEqyY3GZbqxQMyWnTpnba-a5lMc0,4987
|
6
6
|
copyparty/broker_mpw.py,sha256=PpSS4SK3pItlpfD8OwVr3QmJEPKlUgaf2nuMOozixgU,3347
|
7
7
|
copyparty/broker_thr.py,sha256=fjoYtpSscUA7-nMl4r1n2R7UK3J9lrvLS3rUZ-iJzKQ,1721
|
8
8
|
copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
|
9
9
|
copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
|
10
|
-
copyparty/cfg.py,sha256=
|
10
|
+
copyparty/cfg.py,sha256=EilhOIMLopgvn5j72MaRFS0q78K9Xf0NU0I7EAs3YAQ,14269
|
11
11
|
copyparty/dxml.py,sha256=vu5uZQtwvwoqnFHbULs2Zh_y2DETu0T-ENpMZ1i2CV4,2505
|
12
12
|
copyparty/fsutil.py,sha256=NC_CJC4TDag399vVDH9_uQfdfpTMwRFLNxERSWhlVvs,4594
|
13
13
|
copyparty/ftpd.py,sha256=G7PApVIFeSzRo4-D-9uRb8NxYgz6nFwTEbrOk1ErYCU,17969
|
14
|
-
copyparty/httpcli.py,sha256=
|
14
|
+
copyparty/httpcli.py,sha256=pRZtGqx13FDCKpjxc4vlnqEuqVFLaXX7Ylx029bkCIw,222050
|
15
15
|
copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
|
16
16
|
copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
|
17
|
-
copyparty/ico.py,sha256
|
17
|
+
copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
|
18
18
|
copyparty/mdns.py,sha256=G73OWWg1copda47LgayCRK7qjVrk6cnUGpMR5ugmi7o,18315
|
19
19
|
copyparty/metrics.py,sha256=1dim0ShnsD5cfspRbeN9Mt14wOIxPRtxCZY2IScikKw,8974
|
20
20
|
copyparty/mtag.py,sha256=7lINRFc1Vrc-ebP2hU3dBHH3BRFOnEmEZ0jzlpYb8Jg,19910
|
21
21
|
copyparty/multicast.py,sha256=Me4XEEJijvvK2lMRwmGU2hsaI5_E9AEpCjIC4b9UefA,12393
|
22
|
-
copyparty/pwhash.py,sha256=
|
22
|
+
copyparty/pwhash.py,sha256=zHoz9FHGkFBxoRvSfG1XyjN3ibww_h5GE6_m5yS-fws,4246
|
23
23
|
copyparty/smbd.py,sha256=dixFl2wlWymq_Cycc8a4cVB4gY8RSg2e3tE7Xr-aDa0,14614
|
24
24
|
copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
|
25
25
|
copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
|
26
26
|
copyparty/sutil.py,sha256=6zEEGl4hRe6bTB83Y_RtnBGxr2JcUa__GdiAMqNJZnY,3208
|
27
|
-
copyparty/svchub.py,sha256
|
27
|
+
copyparty/svchub.py,sha256=A2yxlg9hzilOypMKPlZOn80YzJ3A6W5pHWWLSAvji0I,45782
|
28
28
|
copyparty/szip.py,sha256=7_RVwHCGk7ON79sAB5HAIUbTEbjDDtn99141aGaI_MA,8596
|
29
29
|
copyparty/tcpsrv.py,sha256=L8jsmFhs6GdJYA58gnsUM1RDakFzHSQGlDcsaeTq-8w,20472
|
30
30
|
copyparty/tftpd.py,sha256=HAXNbIM7I3yFng_a4ubLWGQ4trRTineAZsUPTZDWNQs,14001
|
31
|
-
copyparty/th_cli.py,sha256=
|
32
|
-
copyparty/th_srv.py,sha256
|
33
|
-
copyparty/u2idx.py,sha256=
|
34
|
-
copyparty/up2k.py,sha256=
|
35
|
-
copyparty/util.py,sha256=
|
31
|
+
copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
|
32
|
+
copyparty/th_srv.py,sha256=-V4eNfAYzl-NkJrAjLGqU1cS1dUDS9QLK3iYiXoUz1U,31277
|
33
|
+
copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
|
34
|
+
copyparty/up2k.py,sha256=0Fj2yT2XnJ4MhwdEcDRs1T-gsdxVhdIxdaZSjGyGKBA,177682
|
35
|
+
copyparty/util.py,sha256=qkPD9LFPgLhpmCic_IBvutS2extujDiRrG75PnLMY90,102987
|
36
36
|
copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
37
|
copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
|
38
38
|
copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
|
@@ -55,9 +55,9 @@ copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUu
|
|
55
55
|
copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
|
56
56
|
copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
|
57
57
|
copyparty/web/baguettebox.js.gz,sha256=r2c_hOZV_RTyl4CqWWX14FDWP8nnDVwGkDl4Sfk0rU4,8239
|
58
|
-
copyparty/web/browser.css.gz,sha256=
|
58
|
+
copyparty/web/browser.css.gz,sha256=kPN35ttg9KAACfAB-aZnxFX1GQGqRnK0cLCZXBAu_lc,11654
|
59
59
|
copyparty/web/browser.html,sha256=auvhLVE_t0aIN0q-nk0zOWFqITgDhroMAAviBNLoFfc,4788
|
60
|
-
copyparty/web/browser.js.gz,sha256=
|
60
|
+
copyparty/web/browser.js.gz,sha256=zxztzuGQuEr4SFTy_i_OWLJFe2ZnGWqz7hx0QeXPw8Y,92425
|
61
61
|
copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
|
62
62
|
copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
|
63
63
|
copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
|
@@ -82,7 +82,7 @@ copyparty/web/splash.html,sha256=QEnTH9QZXFmAuyVtgqOuuHKBtIdi7uclpRqe0ZMewj4,624
|
|
82
82
|
copyparty/web/splash.js.gz,sha256=4VqNznN10-bT33IJm3VWzBEJ1s08XZyxFB1TYPUkuAo,2739
|
83
83
|
copyparty/web/svcs.html,sha256=dnE1fG15zOpq7u0GYt8ij6BUv_LTwsiipFeneVYlMsM,14140
|
84
84
|
copyparty/web/svcs.js.gz,sha256=lMXEP9W-VlXyANlva4q0ASSxvvHYlE2CrmxGgZXZop0,713
|
85
|
-
copyparty/web/ui.css.gz,sha256=
|
85
|
+
copyparty/web/ui.css.gz,sha256=iDjrmq32aDN6l2S5AjCQdKjD6bxmzP6ji2WjM1FjKiU,2819
|
86
86
|
copyparty/web/up2k.js.gz,sha256=VQVWBXK2gEz1b8if_ujXHNHnfBO7cdrKoSjqX397VUI,24519
|
87
87
|
copyparty/web/util.js.gz,sha256=rD9iLfVLKRhxC8hmakal-s18xN_rs6GuOqyRPii6HQ8,15110
|
88
88
|
copyparty/web/w.hash.js.gz,sha256=JhJagnqIkcKng_hs6otEgzcuQE7keToG_r5dd2o3EfU,1108
|
@@ -100,7 +100,7 @@ copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ
|
|
100
100
|
copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
|
101
101
|
copyparty/web/deps/easymde.js.gz,sha256=rHBs4XWQe2bmv7ZzDIk43oxnTwrwpq5laYHhV5sKQQo,77014
|
102
102
|
copyparty/web/deps/fuse.py,sha256=6j4Zy3VpQg629pwwIW77v2LJ1hy-qlyrxwhXfKl9B7I,33426
|
103
|
-
copyparty/web/deps/marked.js.gz,sha256=
|
103
|
+
copyparty/web/deps/marked.js.gz,sha256=6GrdpSikQ-tt9GrcAl7wA7mp_Il_2T2ZvBeACVX1j1k,22665
|
104
104
|
copyparty/web/deps/mini-fa.css.gz,sha256=CTPrNaH8OTVmxajrGP88E2MkjadY9_81TBVnd9sw9Y8,572
|
105
105
|
copyparty/web/deps/mini-fa.woff,sha256=L9DNncV2TIyvsrspMbJouvnnt7F068Hbn7YZYvN76AU,2784
|
106
106
|
copyparty/web/deps/prism.css.gz,sha256=Z_A6rJ3MN5KWnjvXaV787aTW_5DT-xjFd0YZ7_W-Krk,1468
|
@@ -109,9 +109,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
|
|
109
109
|
copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
|
110
110
|
copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
|
111
111
|
copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
|
112
|
-
copyparty-1.16.
|
113
|
-
copyparty-1.16.
|
114
|
-
copyparty-1.16.
|
115
|
-
copyparty-1.16.
|
116
|
-
copyparty-1.16.
|
117
|
-
copyparty-1.16.
|
112
|
+
copyparty-1.16.19.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
|
113
|
+
copyparty-1.16.19.dist-info/METADATA,sha256=-QmQvp2dZJ8RVVHlm6Pf4B6WUpiNJNnBUXCwmyMExAI,160161
|
114
|
+
copyparty-1.16.19.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
115
|
+
copyparty-1.16.19.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
|
116
|
+
copyparty-1.16.19.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
|
117
|
+
copyparty-1.16.19.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|