copyparty 1.17.2__py3-none-any.whl → 1.18.0__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/__main__.py +14 -0
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +32 -14
- copyparty/cfg.py +14 -0
- copyparty/httpcli.py +170 -7
- copyparty/util.py +21 -5
- 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/web/util.js.gz +0 -0
- {copyparty-1.17.2.dist-info → copyparty-1.18.0.dist-info}/METADATA +30 -4
- {copyparty-1.17.2.dist-info → copyparty-1.18.0.dist-info}/RECORD +17 -17
- {copyparty-1.17.2.dist-info → copyparty-1.18.0.dist-info}/WHEEL +0 -0
- {copyparty-1.17.2.dist-info → copyparty-1.18.0.dist-info}/entry_points.txt +0 -0
- {copyparty-1.17.2.dist-info → copyparty-1.18.0.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.17.2.dist-info → copyparty-1.18.0.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -956,6 +956,7 @@ def add_general(ap, nc, srvname):
|
|
956
956
|
ap2.add_argument("--name", metavar="TXT", type=u, default=srvname, help="server name (displayed topleft in browser and in mDNS)")
|
957
957
|
ap2.add_argument("--mime", metavar="EXT=MIME", type=u, action="append", help="map file \033[33mEXT\033[0mension to \033[33mMIME\033[0mtype, for example [\033[32mjpg=image/jpeg\033[0m]")
|
958
958
|
ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit")
|
959
|
+
ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)")
|
959
960
|
ap2.add_argument("--license", action="store_true", help="show licenses and exit")
|
960
961
|
ap2.add_argument("--version", action="store_true", help="show versions and exit")
|
961
962
|
|
@@ -1262,6 +1263,7 @@ def add_optouts(ap):
|
|
1262
1263
|
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
|
1263
1264
|
ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
|
1264
1265
|
ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
|
1266
|
+
ap2.add_argument("--no-tail", action="store_true", help="disable streaming a growing files with ?tail (volflag=notail)")
|
1265
1267
|
ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader-IP into the database; will also disable unpost, you may want \033[32m--forget-ip\033[0m instead (volflag=no_db_ip)")
|
1266
1268
|
|
1267
1269
|
|
@@ -1391,6 +1393,16 @@ def add_transcoding(ap):
|
|
1391
1393
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
|
1392
1394
|
|
1393
1395
|
|
1396
|
+
def add_tail(ap):
|
1397
|
+
ap2 = ap.add_argument_group('tailing options (realtime streaming of a growing file)')
|
1398
|
+
ap2.add_argument("--tail-who", metavar="LVL", type=int, default=2, help="who can tail? [\033[32m0\033[0m]=nobody, [\033[32m1\033[0m]=admins, [\033[32m2\033[0m]=authenticated-with-read-access, [\033[32m3\033[0m]=everyone-with-read-access (volflag=tail_who)")
|
1399
|
+
ap2.add_argument("--tail-cmax", metavar="N", type=int, default=64, help="do not allow starting a new tail if more than \033[33mN\033[0m active downloads")
|
1400
|
+
ap2.add_argument("--tail-tmax", metavar="SEC", type=float, default=0, help="terminate connection after \033[33mSEC\033[0m seconds; [\033[32m0\033[0m]=never (volflag=tail_tmax)")
|
1401
|
+
ap2.add_argument("--tail-rate", metavar="SEC", type=float, default=0.2, help="check for new data every \033[33mSEC\033[0m seconds (volflag=tail_rate)")
|
1402
|
+
ap2.add_argument("--tail-ka", metavar="SEC", type=float, default=3.0, help="send a zerobyte if connection is idle for \033[33mSEC\033[0m seconds to prevent disconnect")
|
1403
|
+
ap2.add_argument("--tail-fd", metavar="SEC", type=float, default=1.0, help="check if file was replaced (new fd) if idle for \033[33mSEC\033[0m seconds (volflag=tail_fd)")
|
1404
|
+
|
1405
|
+
|
1394
1406
|
def add_rss(ap):
|
1395
1407
|
ap2 = ap.add_argument_group('RSS options')
|
1396
1408
|
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
|
@@ -1486,6 +1498,7 @@ def add_ui(ap, retry):
|
|
1486
1498
|
ap2.add_argument("--sort", metavar="C,C,C", type=u, default="href", help="default sort order, comma-separated column IDs (see header tooltips), prefix with '-' for descending. Examples: \033[32mhref -href ext sz ts tags/Album tags/.tn\033[0m (volflag=sort)")
|
1487
1499
|
ap2.add_argument("--nsort", action="store_true", help="default-enable natural sort of filenames with leading numbers (volflag=nsort)")
|
1488
1500
|
ap2.add_argument("--hsortn", metavar="N", type=int, default=2, help="number of sorting rules to include in media URLs by default (volflag=hsortn)")
|
1501
|
+
ap2.add_argument("--see-dots", action="store_true", help="default-enable seeing dotfiles; only takes effect if user has the necessary permissions")
|
1489
1502
|
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching \033[33mREGEX\033[0m in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
1490
1503
|
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
1491
1504
|
ap2.add_argument("--ext-th", metavar="E=VP", type=u, action="append", help="use thumbnail-image \033[33mVP\033[0m for file-extension \033[33mE\033[0m, example: [\033[32mexe=/.res/exe.png\033[0m] (volflag=ext_th)")
|
@@ -1595,6 +1608,7 @@ def run_argparse(
|
|
1595
1608
|
add_hooks(ap)
|
1596
1609
|
add_stats(ap)
|
1597
1610
|
add_txt(ap)
|
1611
|
+
add_tail(ap)
|
1598
1612
|
add_og(ap)
|
1599
1613
|
add_ui(ap, retry)
|
1600
1614
|
add_admin(ap)
|
copyparty/__version__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
VERSION = (1,
|
4
|
-
CODENAME = "
|
5
|
-
BUILD_DT = (2025,
|
3
|
+
VERSION = (1, 18, 0)
|
4
|
+
CODENAME = "logtail"
|
5
|
+
BUILD_DT = (2025, 6, 22)
|
6
6
|
|
7
7
|
S_VERSION = ".".join(map(str, VERSION))
|
8
8
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
copyparty/authsrv.py
CHANGED
@@ -65,7 +65,9 @@ SSEELOG = " ({})".format(SEE_LOG)
|
|
65
65
|
BAD_CFG = "invalid config; {}".format(SEE_LOG)
|
66
66
|
SBADCFG = " ({})".format(BAD_CFG)
|
67
67
|
|
68
|
-
PTN_U_GRP = re.compile(r"\$\{u%
|
68
|
+
PTN_U_GRP = re.compile(r"\$\{u(%[+-][^}]+)\}")
|
69
|
+
PTN_G_GRP = re.compile(r"\$\{g(%[+-][^}]+)\}")
|
70
|
+
PTN_SIGIL = re.compile(r"(\${[ug][}%])")
|
69
71
|
|
70
72
|
|
71
73
|
class CfgEx(Exception):
|
@@ -958,15 +960,27 @@ class AuthSrv(object):
|
|
958
960
|
un_gn = [("", "")]
|
959
961
|
|
960
962
|
for un, gn in un_gn:
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
if req == "+":
|
966
|
-
if not hit:
|
967
|
-
continue
|
968
|
-
elif hit:
|
963
|
+
rejected = False
|
964
|
+
for ptn in [PTN_U_GRP, PTN_G_GRP]:
|
965
|
+
m = ptn.search(dst0)
|
966
|
+
if not m:
|
969
967
|
continue
|
968
|
+
zs = m.group(1)
|
969
|
+
zs = zs.replace(",%+", "\n%+")
|
970
|
+
zs = zs.replace(",%-", "\n%-")
|
971
|
+
for rule in zs.split("\n"):
|
972
|
+
gnc = rule[2:]
|
973
|
+
if ptn == PTN_U_GRP:
|
974
|
+
# is user member of group?
|
975
|
+
hit = gnc in (un_gns.get(un) or [])
|
976
|
+
else:
|
977
|
+
# is it this specific group?
|
978
|
+
hit = gn == gnc
|
979
|
+
|
980
|
+
if rule.startswith("%+") != hit:
|
981
|
+
rejected = True
|
982
|
+
if rejected:
|
983
|
+
continue
|
970
984
|
|
971
985
|
# if ap/vp has a user/group placeholder, make sure to keep
|
972
986
|
# track so the same user/group is mapped when setting perms;
|
@@ -981,6 +995,8 @@ class AuthSrv(object):
|
|
981
995
|
|
982
996
|
src = src1.replace("${g}", gn or "\n")
|
983
997
|
dst = dst1.replace("${g}", gn or "\n")
|
998
|
+
src = PTN_G_GRP.sub(gn or "\n", src)
|
999
|
+
dst = PTN_G_GRP.sub(gn or "\n", dst)
|
984
1000
|
if src == src1 and dst == dst1:
|
985
1001
|
gn = ""
|
986
1002
|
|
@@ -1855,7 +1871,7 @@ class AuthSrv(object):
|
|
1855
1871
|
is_shr = shr and zv.vpath.split("/")[0] == shr
|
1856
1872
|
if histp and not is_shr and histp in rhisttab:
|
1857
1873
|
zv2 = rhisttab[histp]
|
1858
|
-
t = "invalid config; multiple volumes share the same histpath (database+thumbnails location):\n histpath: %s\n volume 1: /%s [%s]\n volume 2:
|
1874
|
+
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]"
|
1859
1875
|
t = t % (histp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
1860
1876
|
self.log(t, 1)
|
1861
1877
|
raise Exception(t)
|
@@ -1869,7 +1885,7 @@ class AuthSrv(object):
|
|
1869
1885
|
is_shr = shr and zv.vpath.split("/")[0] == shr
|
1870
1886
|
if dbp and not is_shr and dbp in rdbpaths:
|
1871
1887
|
zv2 = rdbpaths[dbp]
|
1872
|
-
t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2:
|
1888
|
+
t = "invalid config; multiple volumes share the same dbpath (database location):\n dbpath: %s\n volume 1: /%s [%s]\n volume 2: /%s [%s]"
|
1873
1889
|
t = t % (dbp, zv2.vpath, zv2.realpath, zv.vpath, zv.realpath)
|
1874
1890
|
self.log(t, 1)
|
1875
1891
|
raise Exception(t)
|
@@ -2052,12 +2068,13 @@ class AuthSrv(object):
|
|
2052
2068
|
if vf not in vol.flags:
|
2053
2069
|
vol.flags[vf] = getattr(self.args, ga)
|
2054
2070
|
|
2055
|
-
zs = "forget_ip nrand u2abort u2ow ups_who zip_who"
|
2071
|
+
zs = "forget_ip nrand tail_who u2abort u2ow ups_who zip_who"
|
2056
2072
|
for k in zs.split():
|
2057
2073
|
if k in vol.flags:
|
2058
2074
|
vol.flags[k] = int(vol.flags[k])
|
2059
2075
|
|
2060
|
-
|
2076
|
+
zs = "convt tail_fd tail_rate tail_tmax"
|
2077
|
+
for k in zs.split():
|
2061
2078
|
if k in vol.flags:
|
2062
2079
|
vol.flags[k] = float(vol.flags[k])
|
2063
2080
|
|
@@ -2386,7 +2403,7 @@ class AuthSrv(object):
|
|
2386
2403
|
idp_vn, _ = vfs.get(idp_vp, "*", False, False)
|
2387
2404
|
idp_vp0 = idp_vn.vpath0
|
2388
2405
|
|
2389
|
-
sigils = set(
|
2406
|
+
sigils = set(PTN_SIGIL.findall(idp_vp0))
|
2390
2407
|
if len(sigils) > 1:
|
2391
2408
|
t = '\nWARNING: IdP-volume "/%s" created by "/%s" has multiple IdP placeholders: %s'
|
2392
2409
|
self.idp_warn.append(t % (idp_vp, idp_vp0, list(sigils)))
|
@@ -2556,6 +2573,7 @@ class AuthSrv(object):
|
|
2556
2573
|
"txt_ext": self.args.textfiles.replace(",", " "),
|
2557
2574
|
"def_hcols": list(vf.get("mth") or []),
|
2558
2575
|
"unlist0": vf.get("unlist") or "",
|
2576
|
+
"see_dots": self.args.see_dots,
|
2559
2577
|
"dgrid": "grid" in vf,
|
2560
2578
|
"dgsel": "gsel" in vf,
|
2561
2579
|
"dnsort": "nsort" in vf,
|
copyparty/cfg.py
CHANGED
@@ -22,6 +22,7 @@ def vf_bmap() :
|
|
22
22
|
"no_forget": "noforget",
|
23
23
|
"no_pipe": "nopipe",
|
24
24
|
"no_robots": "norobots",
|
25
|
+
"no_tail": "notail",
|
25
26
|
"no_thumb": "dthumb",
|
26
27
|
"no_vthumb": "dvthumb",
|
27
28
|
"no_athumb": "dathumb",
|
@@ -51,6 +52,7 @@ def vf_bmap() :
|
|
51
52
|
"og_no_head",
|
52
53
|
"og_s_title",
|
53
54
|
"rand",
|
55
|
+
"rmagic",
|
54
56
|
"rss",
|
55
57
|
"wo_up_readme",
|
56
58
|
"xdev",
|
@@ -101,6 +103,10 @@ def vf_vmap() :
|
|
101
103
|
"mv_retry",
|
102
104
|
"rm_retry",
|
103
105
|
"sort",
|
106
|
+
"tail_fd",
|
107
|
+
"tail_rate",
|
108
|
+
"tail_tmax",
|
109
|
+
"tail_who",
|
104
110
|
"tcolor",
|
105
111
|
"unlist",
|
106
112
|
"u2abort",
|
@@ -304,6 +310,13 @@ flagcats = {
|
|
304
310
|
"exp_md": "placeholders to expand in markdown files; see --help",
|
305
311
|
"exp_lg": "placeholders to expand in prologue/epilogue; see --help",
|
306
312
|
},
|
313
|
+
"tailing": {
|
314
|
+
"notail": "disable ?tail (download a growing file continuously)",
|
315
|
+
"tail_fd=1": "check if file was replaced (new fd) every 1 sec",
|
316
|
+
"tail_rate=0.2": "check for new data every 0.2 sec",
|
317
|
+
"tail_tmax=30": "kill connection after 30 sec",
|
318
|
+
"tail_who=2": "restrict ?tail access (1=admins,2=authed,3=everyone)",
|
319
|
+
},
|
307
320
|
"others": {
|
308
321
|
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
|
309
322
|
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
|
@@ -312,6 +325,7 @@ flagcats = {
|
|
312
325
|
"dks": "per-directory accesskeys allow browsing into subdirs",
|
313
326
|
"dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so',
|
314
327
|
"rss": "allow '?rss' URL suffix (experimental)",
|
328
|
+
"rmagic": "expensive analysis for mimetype accuracy",
|
315
329
|
"ups_who=2": "restrict viewing the list of recent uploads",
|
316
330
|
"zip_who=2": "restrict access to download-as-zip/tar",
|
317
331
|
"zipmaxn=9k": "reject download-as-zip if more than 9000 files",
|
copyparty/httpcli.py
CHANGED
@@ -1407,7 +1407,13 @@ class HttpCli(object):
|
|
1407
1407
|
except:
|
1408
1408
|
pass
|
1409
1409
|
|
1410
|
+
ap = ""
|
1411
|
+
use_magic = "rmagic" in self.vn.flags
|
1412
|
+
|
1410
1413
|
for i in hits:
|
1414
|
+
if use_magic:
|
1415
|
+
ap = os.path.join(self.vn.realpath, i["rp"])
|
1416
|
+
|
1411
1417
|
iurl = html_escape("%s%s" % (baseurl, i["rp"]), True, True)
|
1412
1418
|
title = unquotep(i["rp"].split("?")[0].split("/")[-1])
|
1413
1419
|
title = html_escape(title, True, True)
|
@@ -1415,7 +1421,7 @@ class HttpCli(object):
|
|
1415
1421
|
tag_a = str(i["tags"].get("artist") or "")
|
1416
1422
|
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
|
1417
1423
|
desc = html_escape(desc, True, True) if desc else title
|
1418
|
-
mime = html_escape(guess_mime(title))
|
1424
|
+
mime = html_escape(guess_mime(title, ap))
|
1419
1425
|
lmod = formatdate(max(0, i["ts"]))
|
1420
1426
|
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
|
1421
1427
|
zs = (
|
@@ -1568,6 +1574,9 @@ class HttpCli(object):
|
|
1568
1574
|
None, 207, "text/xml; charset=" + enc, {"Transfer-Encoding": "chunked"}
|
1569
1575
|
)
|
1570
1576
|
|
1577
|
+
ap = ""
|
1578
|
+
use_magic = "rmagic" in vn.flags
|
1579
|
+
|
1571
1580
|
ret = '<?xml version="1.0" encoding="{}"?>\n<D:multistatus xmlns:D="DAV:">'
|
1572
1581
|
ret = ret.format(uenc)
|
1573
1582
|
for x in fgen:
|
@@ -1594,7 +1603,9 @@ class HttpCli(object):
|
|
1594
1603
|
"supportedlock": '<D:lockentry xmlns:D="DAV:"><D:lockscope><D:exclusive/></D:lockscope><D:locktype><D:write/></D:locktype></D:lockentry>',
|
1595
1604
|
}
|
1596
1605
|
if not isdir:
|
1597
|
-
|
1606
|
+
if use_magic:
|
1607
|
+
ap = os.path.join(tap, x["vp"])
|
1608
|
+
pvs["getcontenttype"] = html_escape(guess_mime(rp, ap))
|
1598
1609
|
pvs["getcontentlength"] = str(st.st_size)
|
1599
1610
|
|
1600
1611
|
for k, v in pvs.items():
|
@@ -2705,6 +2716,7 @@ class HttpCli(object):
|
|
2705
2716
|
locked = chashes # remaining chunks to be received in this request
|
2706
2717
|
written = [] # chunks written to disk, but not yet released by up2k
|
2707
2718
|
num_left = -1 # num chunks left according to most recent up2k release
|
2719
|
+
bail1 = False # used in sad path to avoid contradicting error-text
|
2708
2720
|
treport = time.time() # ratelimit up2k reporting to reduce overhead
|
2709
2721
|
|
2710
2722
|
if "x-up2k-subc" in self.headers:
|
@@ -2843,7 +2855,6 @@ class HttpCli(object):
|
|
2843
2855
|
except:
|
2844
2856
|
# maybe busted handle (eg. disk went full)
|
2845
2857
|
f.close()
|
2846
|
-
chashes = [] # exception flag
|
2847
2858
|
raise
|
2848
2859
|
finally:
|
2849
2860
|
if locked:
|
@@ -2852,13 +2863,14 @@ class HttpCli(object):
|
|
2852
2863
|
num_left, t = x.get()
|
2853
2864
|
if num_left < 0:
|
2854
2865
|
self.loud_reply(t, status=500)
|
2855
|
-
|
2856
|
-
return False
|
2866
|
+
bail1 = True
|
2857
2867
|
else:
|
2858
2868
|
t = "got %d more chunks, %d left"
|
2859
2869
|
self.log(t % (len(written), num_left), 6)
|
2860
2870
|
|
2861
2871
|
if num_left < 0:
|
2872
|
+
if bail1:
|
2873
|
+
return False
|
2862
2874
|
raise Pebkac(500, "unconfirmed; see serverlog")
|
2863
2875
|
|
2864
2876
|
if not num_left and fpool:
|
@@ -3795,6 +3807,20 @@ class HttpCli(object):
|
|
3795
3807
|
|
3796
3808
|
return txt
|
3797
3809
|
|
3810
|
+
def _can_tail(self, volflags ) :
|
3811
|
+
zp = self.args.ua_nodoc
|
3812
|
+
if zp and zp.search(self.ua):
|
3813
|
+
t = "this URL contains no valuable information for bots/crawlers"
|
3814
|
+
raise Pebkac(403, t)
|
3815
|
+
lvl = volflags["tail_who"]
|
3816
|
+
if "notail" in volflags or not lvl:
|
3817
|
+
raise Pebkac(400, "tail is disabled in server config")
|
3818
|
+
elif lvl <= 1 and not self.can_admin:
|
3819
|
+
raise Pebkac(400, "tail is admin-only on this server")
|
3820
|
+
elif lvl <= 2 and self.uname in ("", "*"):
|
3821
|
+
raise Pebkac(400, "you must be authenticated to use ?tail on this server")
|
3822
|
+
return True
|
3823
|
+
|
3798
3824
|
def _can_zip(self, volflags ) :
|
3799
3825
|
lvl = volflags["zip_who"]
|
3800
3826
|
if self.args.no_zip or not lvl:
|
@@ -3939,6 +3965,8 @@ class HttpCli(object):
|
|
3939
3965
|
logmsg = "{:4} {} ".format("", self.req)
|
3940
3966
|
logtail = ""
|
3941
3967
|
|
3968
|
+
is_tail = "tail" in self.uparam and self._can_tail(self.vn.flags)
|
3969
|
+
|
3942
3970
|
if ptop is not None:
|
3943
3971
|
ap_data = "<%s>" % (req_path,)
|
3944
3972
|
try:
|
@@ -4051,6 +4079,7 @@ class HttpCli(object):
|
|
4051
4079
|
and can_range
|
4052
4080
|
and file_sz
|
4053
4081
|
and "," not in hrange
|
4082
|
+
and not is_tail
|
4054
4083
|
):
|
4055
4084
|
try:
|
4056
4085
|
if not hrange.lower().startswith("bytes"):
|
@@ -4119,6 +4148,8 @@ class HttpCli(object):
|
|
4119
4148
|
mime = "text/plain; charset={}".format(self.uparam["txt"] or "utf-8")
|
4120
4149
|
elif "mime" in self.uparam:
|
4121
4150
|
mime = str(self.uparam.get("mime"))
|
4151
|
+
elif "rmagic" in self.vn.flags:
|
4152
|
+
mime = guess_mime(req_path, fs_path)
|
4122
4153
|
else:
|
4123
4154
|
mime = guess_mime(req_path)
|
4124
4155
|
|
@@ -4136,13 +4167,18 @@ class HttpCli(object):
|
|
4136
4167
|
return True
|
4137
4168
|
|
4138
4169
|
dls = self.conn.hsrv.dls
|
4170
|
+
if is_tail:
|
4171
|
+
upper = 1 << 30
|
4172
|
+
if len(dls) > self.args.tail_cmax:
|
4173
|
+
raise Pebkac(400, "too many active downloads to start a new tail")
|
4174
|
+
|
4139
4175
|
if upper - lower > 0x400000: # 4m
|
4140
4176
|
now = time.time()
|
4141
4177
|
self.dl_id = "%s:%s" % (self.ip, self.addr[1])
|
4142
4178
|
dls[self.dl_id] = (now, 0)
|
4143
4179
|
self.conn.hsrv.dli[self.dl_id] = (
|
4144
4180
|
now,
|
4145
|
-
upper - lower,
|
4181
|
+
0 if is_tail else upper - lower,
|
4146
4182
|
self.vn,
|
4147
4183
|
self.vpath,
|
4148
4184
|
self.uname,
|
@@ -4152,6 +4188,9 @@ class HttpCli(object):
|
|
4152
4188
|
return self.tx_pipe(
|
4153
4189
|
ptop, req_path, ap_data, job, lower, upper, status, mime, logmsg
|
4154
4190
|
)
|
4191
|
+
elif is_tail:
|
4192
|
+
self.tx_tail(open_args, status, mime)
|
4193
|
+
return False
|
4155
4194
|
|
4156
4195
|
ret = True
|
4157
4196
|
with open_func(*open_args) as f:
|
@@ -4181,6 +4220,131 @@ class HttpCli(object):
|
|
4181
4220
|
|
4182
4221
|
return ret
|
4183
4222
|
|
4223
|
+
def tx_tail(
|
4224
|
+
self,
|
4225
|
+
open_args ,
|
4226
|
+
status ,
|
4227
|
+
mime ,
|
4228
|
+
) :
|
4229
|
+
vf = self.vn.flags
|
4230
|
+
self.send_headers(length=None, status=status, mime=mime)
|
4231
|
+
abspath = open_args[0]
|
4232
|
+
sec_rate = vf["tail_rate"]
|
4233
|
+
sec_max = vf["tail_tmax"]
|
4234
|
+
sec_fd = vf["tail_fd"]
|
4235
|
+
sec_ka = self.args.tail_ka
|
4236
|
+
wr_slp = self.args.s_wr_slp
|
4237
|
+
wr_sz = self.args.s_wr_sz
|
4238
|
+
dls = self.conn.hsrv.dls
|
4239
|
+
dl_id = self.dl_id
|
4240
|
+
|
4241
|
+
# non-numeric = full file from start
|
4242
|
+
# positive = absolute offset from start
|
4243
|
+
# negative = start that many bytes from eof
|
4244
|
+
try:
|
4245
|
+
ofs = int(self.uparam["tail"])
|
4246
|
+
except:
|
4247
|
+
ofs = 0
|
4248
|
+
|
4249
|
+
t0 = time.time()
|
4250
|
+
ofs0 = ofs
|
4251
|
+
f = None
|
4252
|
+
try:
|
4253
|
+
st = os.stat(abspath)
|
4254
|
+
f = open(*open_args)
|
4255
|
+
f.seek(0, os.SEEK_END)
|
4256
|
+
eof = f.tell()
|
4257
|
+
f.seek(0)
|
4258
|
+
if ofs < 0:
|
4259
|
+
ofs = max(0, ofs + eof)
|
4260
|
+
|
4261
|
+
self.log("tailing from byte %d: %r" % (ofs, abspath), 6)
|
4262
|
+
|
4263
|
+
# send initial data asap
|
4264
|
+
remains = sendfile_py(
|
4265
|
+
self.log, # d/c
|
4266
|
+
ofs,
|
4267
|
+
eof,
|
4268
|
+
f,
|
4269
|
+
self.s,
|
4270
|
+
wr_sz,
|
4271
|
+
wr_slp,
|
4272
|
+
False, # d/c
|
4273
|
+
dls,
|
4274
|
+
dl_id,
|
4275
|
+
)
|
4276
|
+
sent = (eof - ofs) - remains
|
4277
|
+
ofs = eof - remains
|
4278
|
+
f.seek(ofs)
|
4279
|
+
|
4280
|
+
try:
|
4281
|
+
st2 = os.stat(open_args[0])
|
4282
|
+
if st.st_ino == st2.st_ino:
|
4283
|
+
st = st2 # for filesize
|
4284
|
+
except:
|
4285
|
+
pass
|
4286
|
+
|
4287
|
+
gone = 0
|
4288
|
+
t_fd = t_ka = time.time()
|
4289
|
+
while True:
|
4290
|
+
buf = f.read(4096)
|
4291
|
+
now = time.time()
|
4292
|
+
|
4293
|
+
if sec_max and now - t0 >= sec_max:
|
4294
|
+
self.log("max duration exceeded; kicking client", 6)
|
4295
|
+
zb = b"\n\n*** max duration exceeded; disconnecting ***\n"
|
4296
|
+
self.s.sendall(zb)
|
4297
|
+
break
|
4298
|
+
|
4299
|
+
if buf:
|
4300
|
+
t_fd = t_ka = now
|
4301
|
+
self.s.sendall(buf)
|
4302
|
+
sent += len(buf)
|
4303
|
+
dls[dl_id] = (time.time(), sent)
|
4304
|
+
continue
|
4305
|
+
|
4306
|
+
time.sleep(sec_rate)
|
4307
|
+
if t_ka < now - sec_ka:
|
4308
|
+
t_ka = now
|
4309
|
+
self.s.send(b"\x00")
|
4310
|
+
if t_fd < now - sec_fd:
|
4311
|
+
try:
|
4312
|
+
st2 = os.stat(open_args[0])
|
4313
|
+
if (
|
4314
|
+
st2.st_ino != st.st_ino
|
4315
|
+
or st2.st_size < sent
|
4316
|
+
or st2.st_size < st.st_size
|
4317
|
+
):
|
4318
|
+
# open new file before closing previous to avoid toctous (open may fail; cannot null f before)
|
4319
|
+
f2 = open(*open_args)
|
4320
|
+
f.close()
|
4321
|
+
f = f2
|
4322
|
+
f.seek(0, os.SEEK_END)
|
4323
|
+
eof = f.tell()
|
4324
|
+
if eof < sent:
|
4325
|
+
ofs = sent = 0 # shrunk; send from start
|
4326
|
+
zb = b"\n\n*** file size decreased -- rewinding to the start of the file ***\n\n"
|
4327
|
+
self.s.sendall(zb)
|
4328
|
+
if ofs0 < 0 and eof > -ofs0:
|
4329
|
+
ofs = eof + ofs0
|
4330
|
+
else:
|
4331
|
+
ofs = sent # just new fd? resume from same ofs
|
4332
|
+
f.seek(ofs)
|
4333
|
+
self.log("reopened at byte %d: %r" % (ofs, abspath), 6)
|
4334
|
+
gone = 0
|
4335
|
+
st = st2
|
4336
|
+
except:
|
4337
|
+
gone += 1
|
4338
|
+
if gone > 3:
|
4339
|
+
self.log("file deleted; disconnecting")
|
4340
|
+
break
|
4341
|
+
except IOError as ex:
|
4342
|
+
if ex.errno not in (errno.EPIPE, errno.ESHUTDOWN, errno.EBADFD):
|
4343
|
+
raise
|
4344
|
+
finally:
|
4345
|
+
if f:
|
4346
|
+
f.close()
|
4347
|
+
|
4184
4348
|
def tx_pipe(
|
4185
4349
|
self,
|
4186
4350
|
ptop ,
|
@@ -4740,7 +4904,6 @@ class HttpCli(object):
|
|
4740
4904
|
if zi == 2 or (zi == 1 and self.avol):
|
4741
4905
|
dl_list = self.get_dls()
|
4742
4906
|
for t0, t1, sent, sz, vp, dl_id, uname in dl_list:
|
4743
|
-
rem = sz - sent
|
4744
4907
|
td = max(0.1, now - t0)
|
4745
4908
|
rd, fn = vsplit(vp)
|
4746
4909
|
if not rd:
|
copyparty/util.py
CHANGED
@@ -153,9 +153,15 @@ try:
|
|
153
153
|
except:
|
154
154
|
HAVE_PSUTIL = False
|
155
155
|
|
156
|
-
|
156
|
+
try:
|
157
|
+
if os.environ.get("PRTY_NO_MAGIC"):
|
158
|
+
raise Exception()
|
159
|
+
|
157
160
|
import magic
|
161
|
+
except:
|
162
|
+
pass
|
158
163
|
|
164
|
+
if TYPE_CHECKING:
|
159
165
|
from .authsrv import VFS
|
160
166
|
from .broker_util import BrokerCli
|
161
167
|
from .up2k import Up2k
|
@@ -1174,8 +1180,6 @@ class Magician(object):
|
|
1174
1180
|
self.magic = None
|
1175
1181
|
|
1176
1182
|
def ext(self, fpath ) :
|
1177
|
-
import magic
|
1178
|
-
|
1179
1183
|
try:
|
1180
1184
|
if self.bad_magic:
|
1181
1185
|
raise Exception()
|
@@ -3065,11 +3069,13 @@ def unescape_cookie(orig ) :
|
|
3065
3069
|
return "".join(ret)
|
3066
3070
|
|
3067
3071
|
|
3068
|
-
def guess_mime(
|
3072
|
+
def guess_mime(
|
3073
|
+
url , path = "", fallback = "application/octet-stream"
|
3074
|
+
) :
|
3069
3075
|
try:
|
3070
3076
|
ext = url.rsplit(".", 1)[1].lower()
|
3071
3077
|
except:
|
3072
|
-
|
3078
|
+
ext = ""
|
3073
3079
|
|
3074
3080
|
ret = MIMES.get(ext)
|
3075
3081
|
|
@@ -3077,6 +3083,16 @@ def guess_mime(url , fallback = "application/octet-stream") :
|
|
3077
3083
|
x = mimetypes.guess_type(url)
|
3078
3084
|
ret = "application/{}".format(x[1]) if x[1] else x[0]
|
3079
3085
|
|
3086
|
+
if not ret and path:
|
3087
|
+
try:
|
3088
|
+
with open(fsenc(path), "rb", 0) as f:
|
3089
|
+
ret = magic.from_buffer(f.read(4096), mime=True)
|
3090
|
+
if ret.startswith("text/htm"):
|
3091
|
+
# avoid serving up HTML content unless there was actually a .html extension
|
3092
|
+
ret = "text/plain"
|
3093
|
+
except Exception as ex:
|
3094
|
+
pass
|
3095
|
+
|
3080
3096
|
if not ret:
|
3081
3097
|
ret = fallback
|
3082
3098
|
|
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
|
copyparty/web/util.js.gz
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.18.0
|
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
|
@@ -66,7 +66,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
66
66
|
* 🔌 protocols: [http](#the-browser) // [webdav](#webdav-server) // [ftp](#ftp-server) // [tftp](#tftp-server) // [smb/cifs](#smb-server)
|
67
67
|
* 📱 [android app](#android-app) // [iPhone shortcuts](#ios-shortcuts)
|
68
68
|
|
69
|
-
👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running
|
69
|
+
👉 **[Get started](#quickstart)!** or visit the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running on a nuc in my basement
|
70
70
|
|
71
71
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [unpost](#unpost) // [thumbnails](#thumbnails) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [md-viewer](#markdown-viewer)
|
72
72
|
|
@@ -114,6 +114,7 @@ made in Norway 🇳🇴
|
|
114
114
|
* [creating a playlist](#creating-a-playlist) - with a standalone mediaplayer or copyparty
|
115
115
|
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
116
116
|
* [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
|
117
|
+
* [textfile viewer](#textfile-viewer) - with realtime streaming of logfiles and such ([demo](https://a.ocv.me/pub/demo/logtail/))
|
117
118
|
* [markdown viewer](#markdown-viewer) - and there are *two* editors
|
118
119
|
* [markdown vars](#markdown-vars) - dynamic docs with serverside variable expansion
|
119
120
|
* [other tricks](#other-tricks)
|
@@ -315,7 +316,8 @@ also see [comparison to similar software](./docs/versus.md)
|
|
315
316
|
* ☑ play video files as audio (converted on server)
|
316
317
|
* ☑ create and play [m3u8 playlists](#playlists)
|
317
318
|
* ☑ image gallery with webm player
|
318
|
-
* ☑ textfile browser with syntax hilighting
|
319
|
+
* ☑ [textfile browser](#textfile-viewer) with syntax hilighting
|
320
|
+
* ☑ realtime streaming of growing files (logfiles and such)
|
319
321
|
* ☑ [thumbnails](#thumbnails)
|
320
322
|
* ☑ ...of images using Pillow, pyvips, or FFmpeg
|
321
323
|
* ☑ ...of videos using FFmpeg
|
@@ -619,6 +621,8 @@ a client can request to see dotfiles in directory listings if global option `-ed
|
|
619
621
|
|
620
622
|
dotfiles do not appear in search results unless one of the above is true, **and** the global option / volflag `dotsrch` is set
|
621
623
|
|
624
|
+
> even if user has permission to see dotfiles, they are default-hidden unless `--see-dots` is set, and/or user has enabled the `dotfiles` option in the settings tab
|
625
|
+
|
622
626
|
config file example, where the same permission to see dotfiles is given in two different ways just for reference:
|
623
627
|
|
624
628
|
```yaml
|
@@ -755,7 +759,10 @@ enabling `multiselect` lets you click files to select them, and then shift-click
|
|
755
759
|
* `multiselect` is mostly intended for phones/tablets, but the `sel` option in the `[⚙️] settings` tab is better suited for desktop use, allowing selection by CTRL-clicking and range-selection with SHIFT-click, all without affecting regular clicking
|
756
760
|
* the `sel` option can be made default globally with `--gsel` or per-volume with volflag `gsel`
|
757
761
|
|
758
|
-
to show `/icons/exe.png` as the thumbnail for all
|
762
|
+
to show `/icons/exe.png` and `/icons/elf.gif` as the thumbnail for all `.exe` and `.elf` files respectively, do this: `--ext-th=exe=/icons/exe.png --ext-th=elf=/icons/elf.gif`
|
763
|
+
* optionally as separate volflags for each mapping; see config file example below
|
764
|
+
* the supported image formats are [jpg, png, gif, webp, ico](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types)
|
765
|
+
* be careful with svg; chrome will crash if you have too many unique svg files showing on the same page (the limit is 250 or so) -- showing the same handful of svg files thousands of times is ok however
|
759
766
|
|
760
767
|
config file example:
|
761
768
|
|
@@ -772,6 +779,7 @@ config file example:
|
|
772
779
|
dthumb # disable ALL thumbnails and audio transcoding
|
773
780
|
dvthumb # only disable video thumbnails
|
774
781
|
ext-th: exe=/ico/exe.png # /ico/exe.png is the thumbnail of *.exe
|
782
|
+
ext-th: elf=/ico/elf.gif # ...and /ico/elf.gif is used for *.elf
|
775
783
|
th-covers: folder.png,folder.jpg,cover.png,cover.jpg # the default
|
776
784
|
```
|
777
785
|
|
@@ -1179,6 +1187,18 @@ not available on iPhones / iPads because AudioContext currently breaks backgroun
|
|
1179
1187
|
due to phone / app settings, android phones may randomly stop playing music when the power saver kicks in, especially at the end of an album -- you can fix it by [disabling power saving](https://user-images.githubusercontent.com/241032/235262123-c328cca9-3930-4948-bd18-3949b9fd3fcf.png) in the [app settings](https://user-images.githubusercontent.com/241032/235262121-2ffc51ae-7821-4310-a322-c3b7a507890c.png) of the browser you use for music streaming (preferably a dedicated one)
|
1180
1188
|
|
1181
1189
|
|
1190
|
+
## textfile viewer
|
1191
|
+
|
1192
|
+
with realtime streaming of logfiles and such ([demo](https://a.ocv.me/pub/demo/logtail/)) , and terminal colors work too
|
1193
|
+
|
1194
|
+
click `-txt-` next to a textfile to open the viewer, which has the following toolbar buttons:
|
1195
|
+
|
1196
|
+
* `✏️ edit` opens the textfile editor
|
1197
|
+
* `📡 follow` starts monitoring the file for changes, streaming new lines in realtime
|
1198
|
+
* similar to `tail -f`
|
1199
|
+
* [link directly](https://a.ocv.me/pub/demo/logtail/?doc=lipsum.txt&tail) to a file with tailing enabled by adding `&tail` to the textviewer URL
|
1200
|
+
|
1201
|
+
|
1182
1202
|
## markdown viewer
|
1183
1203
|
|
1184
1204
|
and there are *two* editors
|
@@ -2477,6 +2497,9 @@ interact with copyparty using non-browser clients
|
|
2477
2497
|
* and for screenshots on macos, see [./contrib/ishare.iscu](./contrib/#ishareiscu)
|
2478
2498
|
* and for screenshots on linux, see [./contrib/flameshot.sh](./contrib/flameshot.sh)
|
2479
2499
|
|
2500
|
+
* [Custom Uploader](https://f-droid.org/en/packages/com.nyx.custom_uploader/) (an Android app) as an alternative to copyparty's own [PartyUP!](#android-app)
|
2501
|
+
* works if you set UploadURL to `https://your.com/foo/?want=url&pw=hunter2` and FormDataName `f`
|
2502
|
+
|
2480
2503
|
* contextlet (web browser integration); see [contrib contextlet](contrib/#send-to-cppcontextletjson)
|
2481
2504
|
|
2482
2505
|
* [igloo irc](https://iglooirc.com/): Method: `post` Host: `https://you.com/up/?want=url&pw=hunter2` Multipart: `yes` File parameter: `f`
|
@@ -2790,6 +2813,7 @@ set any of the following environment variables to disable its associated optiona
|
|
2790
2813
|
| `PRTY_NO_CFSSL` | never attempt to generate self-signed certificates using [cfssl](https://github.com/cloudflare/cfssl) |
|
2791
2814
|
| `PRTY_NO_FFMPEG` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips |
|
2792
2815
|
| `PRTY_NO_FFPROBE` | **audio transcoding** goes byebye, **thumbnailing** must be handled by Pillow/libvips, **metadata-scanning** must be handled by mutagen |
|
2816
|
+
| `PRTY_NO_MAGIC` | do not use [magic](https://pypi.org/project/python-magic/) for filetype detection |
|
2793
2817
|
| `PRTY_NO_MUTAGEN` | do not use [mutagen](https://pypi.org/project/mutagen/) for reading metadata from media files; will fallback to ffprobe |
|
2794
2818
|
| `PRTY_NO_PIL` | disable all [Pillow](https://pypi.org/project/pillow/)-based thumbnail support; will fallback to libvips or ffmpeg |
|
2795
2819
|
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
@@ -2890,5 +2914,7 @@ if there's a wall of base64 in the log (thread stacks) then please include that,
|
|
2890
2914
|
|
2891
2915
|
for build instructions etc, see [./docs/devnotes.md](./docs/devnotes.md)
|
2892
2916
|
|
2917
|
+
specifically you may want to [build the sfx](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#just-the-sfx) or [build from scratch](https://github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md#build-from-scratch)
|
2918
|
+
|
2893
2919
|
see [./docs/TODO.md](./docs/TODO.md) for planned features / fixes / changes
|
2894
2920
|
|
@@ -1,17 +1,17 @@
|
|
1
1
|
copyparty/__init__.py,sha256=TnFSStmHlwlRIClWW8jSHxZpt3dl_kN6_pEnqBqh3mE,2638
|
2
|
-
copyparty/__main__.py,sha256=
|
3
|
-
copyparty/__version__.py,sha256=
|
4
|
-
copyparty/authsrv.py,sha256=
|
2
|
+
copyparty/__main__.py,sha256=gJlKxpBmV39xfiWp8sx5iUZsoJPhFcCAWY5RVpWsrz0,122959
|
3
|
+
copyparty/__version__.py,sha256=QNl11l-yovzXttFgMOYiqG-Rd-5GJHs6EcPWg-ZqCSI,249
|
4
|
+
copyparty/authsrv.py,sha256=K3dIZxJvNhrcPbXJpVIkoxWaDrQf3CYz3X-FYaVlrYw,114905
|
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=pSSeVYticrDsnsrdRtfpUQN-8WRObsqrYtSRroXmgxo,7992
|
10
|
-
copyparty/cfg.py,sha256=
|
10
|
+
copyparty/cfg.py,sha256=6o6aLzLxZ59lusAn_PMGgldc3rEb9yyGnUnGIP5D9M8,15077
|
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=r_M67eEl-qKLR6pf79TTcf_G1o0fJiOcU4fTTll7B34,228170
|
15
15
|
copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
|
16
16
|
copyparty/httpsrv.py,sha256=pxH_Eh8ElBLvOEDejgpP9Bvk65HNEou-03aYIcgXhrs,18090
|
17
17
|
copyparty/ico.py,sha256=-7QjF_jIxnPo4Vr0oUPksQ_U_Ef0HRsSPm3s71idOz8,3879
|
@@ -32,7 +32,7 @@ copyparty/th_cli.py,sha256=IEX5tCb0gw9Z2aRIDL9bwdvJ6g5jhWZ8OEAAz16_xN4,5426
|
|
32
32
|
copyparty/th_srv.py,sha256=2omGprnKGWim6U7fjJAXI41UCZBSxRwZfS0rCDsUDps,32556
|
33
33
|
copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
|
34
34
|
copyparty/up2k.py,sha256=nnR_ZKaopSNBuAjSN3Q5G_UVe6GmYD1NkxJt5QQf02o,178079
|
35
|
-
copyparty/util.py,sha256=
|
35
|
+
copyparty/util.py,sha256=S-dEme_1rKf3qYgxWWahEUQUzgq_kpoO_nPbuKCwLsY,103948
|
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=cjHv6i6VDjCG66W9h0S37l2-Gv7-qKBH-HpTP7kZSr4,11769
|
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=qgL276U3d4GAZmkdq7YaWTBxGulnDvkevXDoCi9_GJk,96443
|
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,9 +82,9 @@ copyparty/web/splash.html,sha256=ouFB1P9g0K-S3LZQtOLfNz3GLXIRjyETkxo9aJZhiYk,624
|
|
82
82
|
copyparty/web/splash.js.gz,sha256=Qh0KoPWKoJ77cyzOwnhUaCTI5XUoPVV3YJURKklqpBg,2739
|
83
83
|
copyparty/web/svcs.html,sha256=cxgrhX9wD0Z_kvidry3aS9ubuGXYDj2f4ehq1X8T1EA,14227
|
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=e3iIflzddmjoyPrun_1jsu9j7fbdonNQLyhEE2oKKOQ,2819
|
86
86
|
copyparty/web/up2k.js.gz,sha256=vf9Kth2JQ4F-XmWhjcGXqy6gA4eOkg0-EfVjAGQsW5o,24794
|
87
|
-
copyparty/web/util.js.gz,sha256=
|
87
|
+
copyparty/web/util.js.gz,sha256=vQj__zSM0cbBN4AoZgwhFrSCh82ZTv4CbzeufXo8tw4,15248
|
88
88
|
copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
|
89
89
|
copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
90
90
|
copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
|
@@ -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=UdxHVVlpRf9k1UivWpLKVQzpzZxfK_O3pleTi_B5f8k,22704
|
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.
|
113
|
-
copyparty-1.
|
114
|
-
copyparty-1.
|
115
|
-
copyparty-1.
|
116
|
-
copyparty-1.
|
117
|
-
copyparty-1.
|
112
|
+
copyparty-1.18.0.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
|
113
|
+
copyparty-1.18.0.dist-info/METADATA,sha256=AgVke4zkEUcHf9Iw-CNV1myTB72KmxPHCua7tI3DYwg,165863
|
114
|
+
copyparty-1.18.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
115
|
+
copyparty-1.18.0.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
|
116
|
+
copyparty-1.18.0.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
|
117
|
+
copyparty-1.18.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|