copyparty 1.18.8__py3-none-any.whl → 1.18.10__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 +0 -4
- copyparty/__main__.py +13 -5
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +10 -3
- copyparty/httpcli.py +85 -35
- copyparty/httpsrv.py +2 -1
- copyparty/mtag.py +5 -1
- copyparty/svchub.py +2 -0
- copyparty/tcpsrv.py +2 -3
- copyparty/th_cli.py +2 -2
- copyparty/th_srv.py +68 -2
- copyparty/up2k.py +27 -21
- copyparty/util.py +15 -0
- copyparty/web/a/u2c.py +3 -1
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.html +1 -1
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/rups.html +1 -0
- copyparty/web/rups.js.gz +0 -0
- copyparty/web/shares.html +1 -0
- copyparty/web/shares.js.gz +0 -0
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.18.8.dist-info → copyparty-1.18.10.dist-info}/METADATA +30 -2
- {copyparty-1.18.8.dist-info → copyparty-1.18.10.dist-info}/RECORD +30 -35
- copyparty/web/dd/2.png +0 -0
- copyparty/web/dd/3.png +0 -0
- copyparty/web/dd/4.png +0 -0
- copyparty/web/dd/5.png +0 -0
- copyparty/web/dd/__init__.py +0 -0
- {copyparty-1.18.8.dist-info → copyparty-1.18.10.dist-info}/WHEEL +0 -0
- {copyparty-1.18.8.dist-info → copyparty-1.18.10.dist-info}/entry_points.txt +0 -0
- {copyparty-1.18.8.dist-info → copyparty-1.18.10.dist-info}/licenses/LICENSE +0 -0
- {copyparty-1.18.8.dist-info → copyparty-1.18.10.dist-info}/top_level.txt +0 -0
copyparty/__init__.py
CHANGED
copyparty/__main__.py
CHANGED
@@ -87,6 +87,10 @@ u = unicode
|
|
87
87
|
printed = []
|
88
88
|
zsid = uuid.uuid4().urn[4:]
|
89
89
|
|
90
|
+
CFG_DEF = [os.environ.get("PRTY_CONFIG", "")]
|
91
|
+
if not CFG_DEF[0]:
|
92
|
+
CFG_DEF.pop()
|
93
|
+
|
90
94
|
|
91
95
|
class RiceFormatter(argparse.HelpFormatter):
|
92
96
|
def __init__(self, *args , **kwargs ) :
|
@@ -566,7 +570,7 @@ def get_sects():
|
|
566
570
|
|
567
571
|
--grp takes groupname:username1,username2,...
|
568
572
|
and groupnames can be used instead of usernames in -v
|
569
|
-
by prefixing the groupname with
|
573
|
+
by prefixing the groupname with @
|
570
574
|
|
571
575
|
list of permissions:
|
572
576
|
"r" (read): list folder contents, download files
|
@@ -982,7 +986,7 @@ def build_flags_desc():
|
|
982
986
|
|
983
987
|
def add_general(ap, nc, srvname):
|
984
988
|
ap2 = ap.add_argument_group('general options')
|
985
|
-
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
|
989
|
+
ap2.add_argument("-c", metavar="PATH", type=u, default=CFG_DEF, action="append", help="add config file")
|
986
990
|
ap2.add_argument("-nc", metavar="NUM", type=int, default=nc, help="max num clients")
|
987
991
|
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores, 0=all")
|
988
992
|
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, \033[33mUSER\033[0m:\033[33mPASS\033[0m; example [\033[32med:wark\033[0m]")
|
@@ -1101,7 +1105,7 @@ def add_tls(ap, cert_path):
|
|
1101
1105
|
ap2 = ap.add_argument_group('SSL/TLS options')
|
1102
1106
|
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls -- force plaintext")
|
1103
1107
|
ap2.add_argument("--https-only", action="store_true", help="disable plaintext -- force tls")
|
1104
|
-
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to TLS certificate")
|
1108
|
+
ap2.add_argument("--cert", metavar="PATH", type=u, default=cert_path, help="path to file containing a concatenation of TLS key and certificate chain")
|
1105
1109
|
ap2.add_argument("--ssl-ver", metavar="LIST", type=u, default="", help="set allowed ssl/tls versions; [\033[32mhelp\033[0m] shows available versions; default is what your python version considers safe")
|
1106
1110
|
ap2.add_argument("--ciphers", metavar="LIST", type=u, default="", help="set allowed ssl/tls ciphers; [\033[32mhelp\033[0m] shows available ciphers")
|
1107
1111
|
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
@@ -1138,6 +1142,7 @@ def add_auth(ap):
|
|
1138
1142
|
ap2.add_argument("--idp-db", metavar="PATH", type=u, default=idp_db, help="where to store the known IdP users/groups (if you run multiple copyparty instances, make sure they use different DBs)")
|
1139
1143
|
ap2.add_argument("--idp-store", metavar="N", type=int, default=1, help="how to use \033[33m--idp-db\033[0m; [\033[32m0\033[0m] = entirely disable, [\033[32m1\033[0m] = write-only (effectively disabled), [\033[32m2\033[0m] = remember users, [\033[32m3\033[0m] = remember users and groups.\nNOTE: Will remember and restore the IdP-volumes of all users for all eternity if set to 2 or 3, even when user is deleted from your IdP")
|
1140
1144
|
ap2.add_argument("--idp-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to use /?idp (the cache management UI)")
|
1145
|
+
ap2.add_argument("--idp-cookie", metavar="S", type=int, default=0, help="generate a session-token for IdP users which is written to cookie \033[33mcppws\033[0m (or \033[33mcppwd\033[0m if plaintext), to reduce the load on the IdP server, lifetime \033[33mS\033[0m seconds.\n └─note: The expiration time is a client hint only; the actual lifetime of the session-token is infinite (until next restart with \033[33m--ses-db\033[0m wiped)")
|
1141
1146
|
ap2.add_argument("--no-bauth", action="store_true", help="disable basic-authentication support; do not accept passwords from the 'Authenticate' header at all. NOTE: This breaks support for the android app")
|
1142
1147
|
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
1143
1148
|
ap2.add_argument("--ses-db", metavar="PATH", type=u, default=ses_db, help="where to store the sessions database (if you run multiple copyparty instances, make sure they use different DBs)")
|
@@ -1435,6 +1440,8 @@ def add_transcoding(ap):
|
|
1435
1440
|
ap2 = ap.add_argument_group('transcoding options')
|
1436
1441
|
ap2.add_argument("--q-opus", metavar="KBPS", type=int, default=128, help="target bitrate for transcoding to opus; set 0 to disable")
|
1437
1442
|
ap2.add_argument("--q-mp3", metavar="QUALITY", type=u, default="q2", help="target quality for transcoding to mp3, for example [\033[32m192k\033[0m] (CBR) or [\033[32mq0\033[0m] (CQ/CRF, q0=maxquality, q9=smallest); set 0 to disable")
|
1443
|
+
ap2.add_argument("--allow-wav", action="store_true", help="allow transcoding to wav (lossless, uncompressed)")
|
1444
|
+
ap2.add_argument("--allow-flac", action="store_true", help="allow transcoding to flac (lossless, compressed)")
|
1438
1445
|
ap2.add_argument("--no-caf", action="store_true", help="disable transcoding to caf-opus (affects iOS v12~v17), will use mp3 instead")
|
1439
1446
|
ap2.add_argument("--no-owa", action="store_true", help="disable transcoding to webm-opus (iOS v18 and later), will use mp3 instead")
|
1440
1447
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
@@ -1540,6 +1547,7 @@ def add_ui(ap, retry):
|
|
1540
1547
|
ap2 = ap.add_argument_group('ui options')
|
1541
1548
|
ap2.add_argument("--grid", action="store_true", help="show grid/thumbnails by default (volflag=grid)")
|
1542
1549
|
ap2.add_argument("--gsel", action="store_true", help="select files in grid by ctrl-click (volflag=gsel)")
|
1550
|
+
ap2.add_argument("--localtime", action="store_true", help="default to local timezone instead of UTC")
|
1543
1551
|
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language; one of the following: \033[32meng nor chi\033[0m")
|
1544
1552
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use (0..7)")
|
1545
1553
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
@@ -1552,7 +1560,7 @@ def add_ui(ap, retry):
|
|
1552
1560
|
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files/folders matching \033[33mREGEX\033[0m in file list. WARNING: Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
1553
1561
|
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")
|
1554
1562
|
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)")
|
1555
|
-
ap2.add_argument("--mpmc",
|
1563
|
+
ap2.add_argument("--mpmc", type=u, default="", help=argparse.SUPPRESS)
|
1556
1564
|
ap2.add_argument("--spinner", metavar="TXT", type=u, default="🌲", help="\033[33memoji\033[0m or \033[33memoji,css\033[0m Example: [\033[32m🥖,padding:0\033[0m]")
|
1557
1565
|
ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html")
|
1558
1566
|
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html")
|
@@ -1777,7 +1785,7 @@ def main(argv = None) :
|
|
1777
1785
|
argv[idx] = nk + ov
|
1778
1786
|
time.sleep(2)
|
1779
1787
|
|
1780
|
-
da = len(argv) == 1
|
1788
|
+
da = len(argv) == 1 and not CFG_DEF
|
1781
1789
|
try:
|
1782
1790
|
if da:
|
1783
1791
|
argv.extend(["--qr"])
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
@@ -1705,7 +1705,8 @@ class AuthSrv(object):
|
|
1705
1705
|
t = "Read-access has been disabled due to failsafe: No volumes were defined by the config-file. This failsafe is to prevent unintended access if this is due to accidental loss of config. You can override this safeguard and allow read/write to the working-directory by adding the following arguments: -v .::rw"
|
1706
1706
|
self.log(t, 1)
|
1707
1707
|
axs = AXS()
|
1708
|
-
|
1708
|
+
zvf = {"tcolor": self.args.tcolor}
|
1709
|
+
vfs = VFS(self.log_func, absreal("."), "", "", axs, zvf)
|
1709
1710
|
if not axs.uread:
|
1710
1711
|
self.badcfg1 = True
|
1711
1712
|
elif "" not in mount:
|
@@ -2742,6 +2743,8 @@ class AuthSrv(object):
|
|
2742
2743
|
"s_name": self.args.bname,
|
2743
2744
|
"have_up2k_idx": "e2d" in vf,
|
2744
2745
|
"have_acode": not self.args.no_acode,
|
2746
|
+
"have_c2flac": self.args.allow_flac,
|
2747
|
+
"have_c2wav": self.args.allow_wav,
|
2745
2748
|
"have_shr": self.args.shr,
|
2746
2749
|
"have_zip": not self.args.no_zip,
|
2747
2750
|
"have_mv": not self.args.no_mv,
|
@@ -2766,6 +2769,7 @@ class AuthSrv(object):
|
|
2766
2769
|
"dth3x": vf["th3x"],
|
2767
2770
|
"dvol": self.args.au_vol,
|
2768
2771
|
"idxh": int(self.args.ih),
|
2772
|
+
"dutc": not self.args.localtime,
|
2769
2773
|
"themes": self.args.themes,
|
2770
2774
|
"turbolvl": self.args.turbo,
|
2771
2775
|
"nosubtle": self.args.nosubtle,
|
@@ -2853,7 +2857,10 @@ class AuthSrv(object):
|
|
2853
2857
|
|
2854
2858
|
n = []
|
2855
2859
|
q = "insert into us values (?,?,?)"
|
2856
|
-
|
2860
|
+
accs = list(self.acct)
|
2861
|
+
if self.args.idp_h_usr and self.args.idp_cookie:
|
2862
|
+
accs.extend(self.idp_accs.keys())
|
2863
|
+
for uname in accs:
|
2857
2864
|
if uname not in ases:
|
2858
2865
|
sid = ub64enc(os.urandom(blen)).decode("ascii")
|
2859
2866
|
cur.execute(q, (uname, sid, int(time.time())))
|
@@ -3444,7 +3451,7 @@ def expand_config_file(
|
|
3444
3451
|
ipath += " -> " + fp
|
3445
3452
|
ret.append("#\033[36m opening cfg file{}\033[0m".format(ipath))
|
3446
3453
|
|
3447
|
-
cfg_lines = read_utf8(log, fp, True).split("\n")
|
3454
|
+
cfg_lines = read_utf8(log, fp, True).replace("\t", " ").split("\n")
|
3448
3455
|
if True: # diff-golf
|
3449
3456
|
for oln in [x.rstrip() for x in cfg_lines]:
|
3450
3457
|
ln = oln.split(" #")[0].strip()
|
copyparty/httpcli.py
CHANGED
@@ -107,6 +107,7 @@ from .util import (
|
|
107
107
|
sendfile_py,
|
108
108
|
set_fperms,
|
109
109
|
stat_resource,
|
110
|
+
str_anchor,
|
110
111
|
ub64dec,
|
111
112
|
ub64enc,
|
112
113
|
ujoin,
|
@@ -652,6 +653,9 @@ class HttpCli(object):
|
|
652
653
|
self.pw = ""
|
653
654
|
self.uname = idp_usr
|
654
655
|
self.html_head += "<script>var is_idp=1</script>\n"
|
656
|
+
zs = self.asrv.ases.get(idp_usr)
|
657
|
+
if zs:
|
658
|
+
self.set_idp_cookie(zs)
|
655
659
|
else:
|
656
660
|
self.log("unknown username: %r" % (idp_usr,), 1)
|
657
661
|
|
@@ -906,7 +910,7 @@ class HttpCli(object):
|
|
906
910
|
if status == 304:
|
907
911
|
self.out_headers.pop("Content-Length", None)
|
908
912
|
self.out_headers.pop("Content-Type", None)
|
909
|
-
self.out_headerlist
|
913
|
+
self.out_headerlist[:] = []
|
910
914
|
if self.k304():
|
911
915
|
self.keepalive = False
|
912
916
|
else:
|
@@ -1190,15 +1194,6 @@ class HttpCli(object):
|
|
1190
1194
|
self.reply(b"ssdp is disabled in server config", 404)
|
1191
1195
|
return False
|
1192
1196
|
|
1193
|
-
if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
|
1194
|
-
if self.args.mpmc == ".":
|
1195
|
-
raise Pebkac(404)
|
1196
|
-
|
1197
|
-
loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
|
1198
|
-
h = {"Location": loc, "Cache-Control": "max-age=39"}
|
1199
|
-
self.reply(b"", 301, headers=h)
|
1200
|
-
return True
|
1201
|
-
|
1202
1197
|
if self.vpath == ".cpr/metrics":
|
1203
1198
|
return self.conn.hsrv.metrics.tx(self)
|
1204
1199
|
|
@@ -2074,16 +2069,16 @@ class HttpCli(object):
|
|
2074
2069
|
rnd, lifetime, xbu, xau = self.upload_flags(vfs)
|
2075
2070
|
lim = vfs.get_dbv(rem)[0].lim
|
2076
2071
|
fdir = vfs.canonical(rem)
|
2077
|
-
if lim:
|
2078
|
-
fdir, rem = lim.all(
|
2079
|
-
self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
|
2080
|
-
)
|
2081
|
-
|
2082
2072
|
fn = None
|
2083
2073
|
if rem and not self.trailing_slash and not bos.path.isdir(fdir):
|
2084
2074
|
fdir, fn = os.path.split(fdir)
|
2085
2075
|
rem, _ = vsplit(rem)
|
2086
2076
|
|
2077
|
+
if lim:
|
2078
|
+
fdir, rem = lim.all(
|
2079
|
+
self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
|
2080
|
+
)
|
2081
|
+
|
2087
2082
|
bos.makedirs(fdir, vf=vfs.flags)
|
2088
2083
|
|
2089
2084
|
open_ka = {"fun": open}
|
@@ -2928,7 +2923,7 @@ class HttpCli(object):
|
|
2928
2923
|
msg = "new password OK"
|
2929
2924
|
|
2930
2925
|
redir = (self.args.SRS + "?h") if ok else ""
|
2931
|
-
h2 = '<a href="' + self.args.SRS + '?h">
|
2926
|
+
h2 = '<a href="' + self.args.SRS + '?h">continue</a>'
|
2932
2927
|
html = self.j2s("msg", h1=msg, h2=h2, redir=redir)
|
2933
2928
|
self.reply(html.encode("utf-8"))
|
2934
2929
|
return True
|
@@ -2956,7 +2951,8 @@ class HttpCli(object):
|
|
2956
2951
|
dst += "_=1#" + html_escape(uhash, True, True)
|
2957
2952
|
|
2958
2953
|
_, msg = self.get_pwd_cookie(pwd)
|
2959
|
-
|
2954
|
+
h2 = '<a href="' + dst + '">continue</a>'
|
2955
|
+
html = self.j2s("msg", h1=msg, h2=h2, redir=dst)
|
2960
2956
|
self.reply(html.encode("utf-8"))
|
2961
2957
|
return True
|
2962
2958
|
|
@@ -2969,7 +2965,7 @@ class HttpCli(object):
|
|
2969
2965
|
self.get_pwd_cookie("x")
|
2970
2966
|
|
2971
2967
|
dst = self.args.SRS + "?h"
|
2972
|
-
h2 = '<a href="' + dst + '">
|
2968
|
+
h2 = '<a href="' + dst + '">continue</a>'
|
2973
2969
|
html = self.j2s("msg", h1="ok bye", h2=h2, redir=dst)
|
2974
2970
|
self.reply(html.encode("utf-8"))
|
2975
2971
|
return True
|
@@ -3022,6 +3018,19 @@ class HttpCli(object):
|
|
3022
3018
|
|
3023
3019
|
return dur > 0, msg
|
3024
3020
|
|
3021
|
+
def set_idp_cookie(self, ases) :
|
3022
|
+
k = "cppws" if self.is_https else "cppwd"
|
3023
|
+
ck = gencookie(
|
3024
|
+
k,
|
3025
|
+
ases,
|
3026
|
+
self.args.R,
|
3027
|
+
self.args.cookie_lax,
|
3028
|
+
self.is_https,
|
3029
|
+
self.args.idp_cookie,
|
3030
|
+
"; HttpOnly",
|
3031
|
+
)
|
3032
|
+
self.out_headers["Set-Cookie"] = ck
|
3033
|
+
|
3025
3034
|
def handle_mkdir(self) :
|
3026
3035
|
new_dir = self.parser.require("name", 512)
|
3027
3036
|
self.parser.drop()
|
@@ -3093,6 +3102,20 @@ class HttpCli(object):
|
|
3093
3102
|
if "fperms" in vfs.flags:
|
3094
3103
|
set_fperms(f, vfs.flags)
|
3095
3104
|
|
3105
|
+
dbv, vrem = vfs.get_dbv(rem)
|
3106
|
+
self.conn.hsrv.broker.say(
|
3107
|
+
"up2k.hash_file",
|
3108
|
+
dbv.realpath,
|
3109
|
+
dbv.vpath,
|
3110
|
+
dbv.flags,
|
3111
|
+
vrem,
|
3112
|
+
sanitized,
|
3113
|
+
self.ip,
|
3114
|
+
bos.stat(fn).st_mtime,
|
3115
|
+
self.uname,
|
3116
|
+
True,
|
3117
|
+
)
|
3118
|
+
|
3096
3119
|
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
3097
3120
|
self.redirect(vpath, "?edit")
|
3098
3121
|
return True
|
@@ -4646,7 +4669,7 @@ class HttpCli(object):
|
|
4646
4669
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
4647
4670
|
cfmt = ""
|
4648
4671
|
if self.thumbcli and not self.args.no_bacode:
|
4649
|
-
for zs in ("opus", "mp3", "w", "j", "p"):
|
4672
|
+
for zs in ("opus", "mp3", "flac", "wav", "w", "j", "p"):
|
4650
4673
|
if zs in self.ouparam or uarg == zs:
|
4651
4674
|
cfmt = zs
|
4652
4675
|
|
@@ -5331,15 +5354,16 @@ class HttpCli(object):
|
|
5331
5354
|
raise Pebkac(500, "sqlite3 not found on server; unpost is disabled")
|
5332
5355
|
raise Pebkac(500, "server busy, cannot unpost; please retry in a bit")
|
5333
5356
|
|
5334
|
-
|
5335
|
-
|
5336
|
-
lm = "ups %r" % (
|
5357
|
+
sfilt = self.uparam.get("filter") or ""
|
5358
|
+
nfi, vfi = str_anchor(sfilt)
|
5359
|
+
lm = "ups %d%r" % (nfi, sfilt)
|
5337
5360
|
|
5338
5361
|
if self.args.shr and self.vpath.startswith(self.args.shr1):
|
5339
5362
|
shr_dbv, shr_vrem = self.vn.get_dbv(self.rem)
|
5340
5363
|
else:
|
5341
5364
|
shr_dbv = None
|
5342
5365
|
|
5366
|
+
wret = {}
|
5343
5367
|
ret = []
|
5344
5368
|
t0 = time.time()
|
5345
5369
|
lim = time.time() - self.args.unpost
|
@@ -5361,7 +5385,13 @@ class HttpCli(object):
|
|
5361
5385
|
x = self.conn.hsrv.broker.ask(
|
5362
5386
|
"up2k.get_unfinished_by_user", self.uname, "" if bad_xff else self.ip
|
5363
5387
|
)
|
5364
|
-
|
5388
|
+
zdsa = x.get()
|
5389
|
+
uret = []
|
5390
|
+
if "timeout" in zdsa:
|
5391
|
+
wret["nou"] = 1
|
5392
|
+
else:
|
5393
|
+
uret = zdsa["f"]
|
5394
|
+
nu = len(uret)
|
5365
5395
|
|
5366
5396
|
if not self.args.unpost:
|
5367
5397
|
allvols = []
|
@@ -5386,8 +5416,14 @@ class HttpCli(object):
|
|
5386
5416
|
q = "select sz, rd, fn, at from up where ip=? and at>? order by at desc"
|
5387
5417
|
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
|
5388
5418
|
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
5389
|
-
if
|
5390
|
-
|
5419
|
+
if nfi == 0 or (nfi == 1 and vfi in vp):
|
5420
|
+
pass
|
5421
|
+
elif nfi == 2:
|
5422
|
+
if not vp.startswith(vfi):
|
5423
|
+
continue
|
5424
|
+
elif nfi == 3:
|
5425
|
+
if not vp.endswith(vfi):
|
5426
|
+
continue
|
5391
5427
|
|
5392
5428
|
n -= 1
|
5393
5429
|
if not n:
|
@@ -5407,6 +5443,8 @@ class HttpCli(object):
|
|
5407
5443
|
|
5408
5444
|
if len(ret) > 2000:
|
5409
5445
|
ret = ret[:2000]
|
5446
|
+
if len(ret) >= 2000:
|
5447
|
+
wret["oc"] = 1
|
5410
5448
|
|
5411
5449
|
for rv in ret:
|
5412
5450
|
rv["vp"] = quotep(rv["vp"])
|
@@ -5426,6 +5464,13 @@ class HttpCli(object):
|
|
5426
5464
|
)
|
5427
5465
|
rv["vp"] += "?k=" + fk[:nfk]
|
5428
5466
|
|
5467
|
+
if not allvols:
|
5468
|
+
wret["noc"] = 1
|
5469
|
+
ret = []
|
5470
|
+
|
5471
|
+
nc = len(ret)
|
5472
|
+
ret = uret + ret
|
5473
|
+
|
5429
5474
|
if shr_dbv:
|
5430
5475
|
# translate vpaths from share-target to share-url
|
5431
5476
|
# to satisfy access checks
|
@@ -5439,12 +5484,11 @@ class HttpCli(object):
|
|
5439
5484
|
for v in ret:
|
5440
5485
|
v["vp"] = self.args.SR + v["vp"]
|
5441
5486
|
|
5442
|
-
|
5443
|
-
|
5444
|
-
|
5445
|
-
jtxt =
|
5446
|
-
|
5447
|
-
self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
|
5487
|
+
wret["f"] = ret
|
5488
|
+
wret["nu"] = nu
|
5489
|
+
wret["nc"] = nc
|
5490
|
+
jtxt = json.dumps(wret, separators=(",\n", ": "))
|
5491
|
+
self.log("%s #%d+%d %.2fsec" % (lm, nu, nc, time.time() - t0))
|
5448
5492
|
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
5449
5493
|
return True
|
5450
5494
|
|
@@ -5459,8 +5503,8 @@ class HttpCli(object):
|
|
5459
5503
|
raise Pebkac(500, "server busy, cannot list recent uploads; please retry")
|
5460
5504
|
|
5461
5505
|
sfilt = self.uparam.get("filter") or ""
|
5462
|
-
|
5463
|
-
lm = "ru %r" % (sfilt
|
5506
|
+
nfi, vfi = str_anchor(sfilt)
|
5507
|
+
lm = "ru %d%r" % (nfi, sfilt)
|
5464
5508
|
self.log(lm)
|
5465
5509
|
|
5466
5510
|
ret = []
|
@@ -5495,8 +5539,14 @@ class HttpCli(object):
|
|
5495
5539
|
q = "select sz, rd, fn, ip, at from up where at>0 order by at desc"
|
5496
5540
|
for sz, rd, fn, ip, at in cur.execute(q):
|
5497
5541
|
vp = "/" + "/".join(x for x in [vol.vpath, rd, fn] if x)
|
5498
|
-
if
|
5499
|
-
|
5542
|
+
if nfi == 0 or (nfi == 1 and vfi in vp):
|
5543
|
+
pass
|
5544
|
+
elif nfi == 2:
|
5545
|
+
if not vp.startswith(vfi):
|
5546
|
+
continue
|
5547
|
+
elif nfi == 3:
|
5548
|
+
if not vp.endswith(vfi):
|
5549
|
+
continue
|
5500
5550
|
|
5501
5551
|
if not dots and "/." in vp:
|
5502
5552
|
continue
|
copyparty/httpsrv.py
CHANGED
@@ -319,7 +319,8 @@ class HttpSrv(object):
|
|
319
319
|
spins = 0
|
320
320
|
while self.ncli >= self.nclimax:
|
321
321
|
if not spins:
|
322
|
-
|
322
|
+
t = "at connection limit (global-option 'nc'); waiting"
|
323
|
+
self.log(self.name, t, 3)
|
323
324
|
|
324
325
|
spins += 1
|
325
326
|
time.sleep(0.1)
|
copyparty/mtag.py
CHANGED
@@ -61,6 +61,8 @@ HAVE_FFPROBE = not os.environ.get("PRTY_NO_FFPROBE") and have_ff("ffprobe")
|
|
61
61
|
CBZ_PICS = set("png jpg jpeg gif bmp tga tif tiff webp avif".split())
|
62
62
|
CBZ_01 = re.compile(r"(^|[^0-9v])0+[01]\b")
|
63
63
|
|
64
|
+
FMT_AU = set("mp3 ogg flac wav".split())
|
65
|
+
|
64
66
|
|
65
67
|
class MParser(object):
|
66
68
|
def __init__(self, cmdline ) :
|
@@ -236,7 +238,7 @@ def parse_ffprobe(txt ) :
|
|
236
238
|
ret = {} # processed
|
237
239
|
md = {} # raw tags
|
238
240
|
|
239
|
-
is_audio = fmt.get("format_name") in
|
241
|
+
is_audio = fmt.get("format_name") in FMT_AU
|
240
242
|
if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
|
241
243
|
is_audio = True
|
242
244
|
|
@@ -264,6 +266,8 @@ def parse_ffprobe(txt ) :
|
|
264
266
|
["channel_layout", "chs"],
|
265
267
|
["sample_rate", ".hz"],
|
266
268
|
["bit_rate", ".aq"],
|
269
|
+
["bits_per_sample", ".bps"],
|
270
|
+
["bits_per_raw_sample", ".bprs"],
|
267
271
|
["duration", ".dur"],
|
268
272
|
]
|
269
273
|
|
copyparty/svchub.py
CHANGED
@@ -1016,6 +1016,8 @@ class SvcHub(object):
|
|
1016
1016
|
except:
|
1017
1017
|
raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
|
1018
1018
|
|
1019
|
+
al.js_utc = "false" if al.localtime else "true"
|
1020
|
+
|
1019
1021
|
al.tcolor = al.tcolor.lstrip("#")
|
1020
1022
|
if len(al.tcolor) == 3: # fc5 => ffcc55
|
1021
1023
|
al.tcolor = "".join([x * 2 for x in al.tcolor])
|
copyparty/tcpsrv.py
CHANGED
@@ -580,8 +580,7 @@ class TcpSrv(object):
|
|
580
580
|
if not ip:
|
581
581
|
return ""
|
582
582
|
|
583
|
-
if ":" in ip
|
584
|
-
ip = "[{}]".format(ip)
|
583
|
+
hip = "[%s]" % (ip,) if ":" in ip else ip
|
585
584
|
|
586
585
|
if self.args.http_only:
|
587
586
|
https = ""
|
@@ -593,7 +592,7 @@ class TcpSrv(object):
|
|
593
592
|
ports = t1.get(ip, t2.get(ip, []))
|
594
593
|
dport = 443 if https else 80
|
595
594
|
port = "" if dport in ports or not ports else ":{}".format(ports[0])
|
596
|
-
txt = "http{}://{}{}/{}".format(https,
|
595
|
+
txt = "http{}://{}{}/{}".format(https, hip, port, self.args.qrl)
|
597
596
|
|
598
597
|
btxt = txt.encode("utf-8")
|
599
598
|
if PY2:
|
copyparty/th_cli.py
CHANGED
@@ -85,7 +85,7 @@ class ThumbCli(object):
|
|
85
85
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg", "png"]:
|
86
86
|
return os.path.join(ptop, rem)
|
87
87
|
|
88
|
-
if fmt[:1] in "jw":
|
88
|
+
if fmt[:1] in "jw" and fmt != "wav":
|
89
89
|
sfmt = fmt[:1]
|
90
90
|
|
91
91
|
if sfmt == "j" and self.args.th_no_jpg:
|
@@ -126,7 +126,7 @@ class ThumbCli(object):
|
|
126
126
|
|
127
127
|
tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
|
128
128
|
tpaths = [tpath]
|
129
|
-
if fmt[:1] == "w":
|
129
|
+
if fmt[:1] == "w" and fmt != "wav":
|
130
130
|
# also check for jpg (maybe webp is unavailable)
|
131
131
|
tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
|
132
132
|
|
copyparty/th_srv.py
CHANGED
@@ -47,7 +47,7 @@ HAVE_AVIF = False
|
|
47
47
|
HAVE_WEBP = False
|
48
48
|
|
49
49
|
EXTS_TH = set(["jpg", "webp", "png"])
|
50
|
-
EXTS_AC = set(["opus", "owa", "caf", "mp3"])
|
50
|
+
EXTS_AC = set(["opus", "owa", "caf", "mp3", "flac", "wav"])
|
51
51
|
EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
|
52
52
|
|
53
53
|
PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
|
@@ -352,8 +352,10 @@ class ThumbSrv(object):
|
|
352
352
|
tex = tpath.rsplit(".", 1)[-1]
|
353
353
|
want_mp3 = tex == "mp3"
|
354
354
|
want_opus = tex in ("opus", "owa", "caf")
|
355
|
+
want_flac = tex == "flac"
|
356
|
+
want_wav = tex == "wav"
|
355
357
|
want_png = tex == "png"
|
356
|
-
want_au = want_mp3 or want_opus
|
358
|
+
want_au = want_mp3 or want_opus or want_flac or want_wav
|
357
359
|
for lib in self.args.th_dec:
|
358
360
|
can_au = lib == "ff" and (
|
359
361
|
ext in self.fmt_ffa or ext in self.fmt_ffv
|
@@ -368,6 +370,10 @@ class ThumbSrv(object):
|
|
368
370
|
funs.append(self.conv_opus)
|
369
371
|
elif want_mp3:
|
370
372
|
funs.append(self.conv_mp3)
|
373
|
+
elif want_flac:
|
374
|
+
funs.append(self.conv_flac)
|
375
|
+
elif want_wav:
|
376
|
+
funs.append(self.conv_wav)
|
371
377
|
elif want_png:
|
372
378
|
funs.append(self.conv_waves)
|
373
379
|
png_ok = True
|
@@ -803,6 +809,66 @@ class ThumbSrv(object):
|
|
803
809
|
# fmt: on
|
804
810
|
self._run_ff(cmd, vn, oom=300)
|
805
811
|
|
812
|
+
def conv_flac(self, abspath , tpath , fmt , vn ) :
|
813
|
+
if self.args.no_acode or not self.args.allow_flac:
|
814
|
+
raise Exception("flac not permitted in server config")
|
815
|
+
|
816
|
+
self.wait4ram(0.2, tpath)
|
817
|
+
tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
818
|
+
if "ac" not in tags:
|
819
|
+
raise Exception("not audio")
|
820
|
+
|
821
|
+
self.log("conv2 flac", 6)
|
822
|
+
|
823
|
+
# fmt: off
|
824
|
+
cmd = [
|
825
|
+
b"ffmpeg",
|
826
|
+
b"-nostdin",
|
827
|
+
b"-v", b"error",
|
828
|
+
b"-hide_banner",
|
829
|
+
b"-i", fsenc(abspath),
|
830
|
+
b"-map", b"0:a:0",
|
831
|
+
b"-c:a", b"flac",
|
832
|
+
fsenc(tpath)
|
833
|
+
]
|
834
|
+
# fmt: on
|
835
|
+
self._run_ff(cmd, vn, oom=300)
|
836
|
+
|
837
|
+
def conv_wav(self, abspath , tpath , fmt , vn ) :
|
838
|
+
if self.args.no_acode or not self.args.allow_wav:
|
839
|
+
raise Exception("wav not permitted in server config")
|
840
|
+
|
841
|
+
self.wait4ram(0.2, tpath)
|
842
|
+
tags, rawtags = ffprobe(abspath, int(vn.flags["convt"] / 2))
|
843
|
+
if "ac" not in tags:
|
844
|
+
raise Exception("not audio")
|
845
|
+
|
846
|
+
bits = tags[".bps"][1]
|
847
|
+
if bits == 0.0:
|
848
|
+
bits = tags[".bprs"][1]
|
849
|
+
|
850
|
+
codec = b"pcm_s32le"
|
851
|
+
if bits <= 16.0:
|
852
|
+
codec = b"pcm_s16le"
|
853
|
+
elif bits <= 24.0:
|
854
|
+
codec = b"pcm_s24le"
|
855
|
+
|
856
|
+
self.log("conv2 wav", 6)
|
857
|
+
|
858
|
+
# fmt: off
|
859
|
+
cmd = [
|
860
|
+
b"ffmpeg",
|
861
|
+
b"-nostdin",
|
862
|
+
b"-v", b"error",
|
863
|
+
b"-hide_banner",
|
864
|
+
b"-i", fsenc(abspath),
|
865
|
+
b"-map", b"0:a:0",
|
866
|
+
b"-c:a", codec,
|
867
|
+
fsenc(tpath)
|
868
|
+
]
|
869
|
+
# fmt: on
|
870
|
+
self._run_ff(cmd, vn, oom=300)
|
871
|
+
|
806
872
|
def conv_opus(self, abspath , tpath , fmt , vn ) :
|
807
873
|
if self.args.no_acode or not self.args.q_opus:
|
808
874
|
raise Exception("disabled in server config")
|
copyparty/up2k.py
CHANGED
@@ -83,7 +83,10 @@ if TYPE_CHECKING:
|
|
83
83
|
from .svchub import SvcHub
|
84
84
|
|
85
85
|
zsg = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp"
|
86
|
-
|
86
|
+
ICV_EXTS = set(zsg.split(","))
|
87
|
+
|
88
|
+
zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv"
|
89
|
+
VCV_EXTS = set(zsg.split(","))
|
87
90
|
|
88
91
|
zsg = "nohash noidx xdev xvol"
|
89
92
|
VF_AFFECTS_INDEXING = set(zsg.split(" "))
|
@@ -395,12 +398,14 @@ class Up2k(object):
|
|
395
398
|
|
396
399
|
return "{}"
|
397
400
|
|
398
|
-
def get_unfinished_by_user(self, uname, ip)
|
401
|
+
def get_unfinished_by_user(self, uname, ip) :
|
402
|
+
# returns dict due to ExceptionalQueue
|
399
403
|
if PY2 or not self.reg_mutex.acquire(timeout=2):
|
400
|
-
return
|
404
|
+
return {"timeout": 1}
|
401
405
|
|
402
406
|
ret = []
|
403
407
|
userset = set([(uname or "\n"), "*"])
|
408
|
+
n = 1000
|
404
409
|
try:
|
405
410
|
for ptop, tab2 in self.registry.items():
|
406
411
|
cfg = self.flags.get(ptop, {}).get("u2abort", 1)
|
@@ -415,7 +420,6 @@ class Up2k(object):
|
|
415
420
|
or (addr and addr != job["addr"])
|
416
421
|
):
|
417
422
|
continue
|
418
|
-
|
419
423
|
zt5 = (
|
420
424
|
int(job["t0"]),
|
421
425
|
djoin(job["vtop"], job["prel"], job["name"]),
|
@@ -424,6 +428,9 @@ class Up2k(object):
|
|
424
428
|
len(job["hash"]),
|
425
429
|
)
|
426
430
|
ret.append(zt5)
|
431
|
+
n -= 1
|
432
|
+
if not n:
|
433
|
+
break
|
427
434
|
finally:
|
428
435
|
self.reg_mutex.release()
|
429
436
|
|
@@ -440,7 +447,7 @@ class Up2k(object):
|
|
440
447
|
}
|
441
448
|
for (at, vp, sz, nn, nh) in ret
|
442
449
|
]
|
443
|
-
return
|
450
|
+
return {"f": ret2}
|
444
451
|
|
445
452
|
def get_unfinished(self) :
|
446
453
|
if PY2 or not self.reg_mutex.acquire(timeout=0.5):
|
@@ -1466,7 +1473,7 @@ class Up2k(object):
|
|
1466
1473
|
unreg = []
|
1467
1474
|
files = []
|
1468
1475
|
fat32 = True
|
1469
|
-
cv = ""
|
1476
|
+
cv = vcv = ""
|
1470
1477
|
|
1471
1478
|
th_cvd = self.args.th_coversd
|
1472
1479
|
th_cvds = self.args.th_coversd_set
|
@@ -1560,25 +1567,24 @@ class Up2k(object):
|
|
1560
1567
|
|
1561
1568
|
rsz += sz
|
1562
1569
|
files.append((sz, lmod, iname))
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1570
|
+
if sz:
|
1571
|
+
liname = iname.lower()
|
1572
|
+
ext = liname.rsplit(".", 1)[-1]
|
1573
|
+
if (
|
1567
1574
|
liname in th_cvds
|
1568
|
-
or (
|
1569
|
-
|
1570
|
-
and liname.rsplit(".", 1)[-1] in CV_EXTS
|
1571
|
-
and not iname.startswith(".")
|
1572
|
-
)
|
1573
|
-
)
|
1574
|
-
and (
|
1575
|
+
or (not cv and ext in ICV_EXTS and not iname.startswith("."))
|
1576
|
+
) and (
|
1575
1577
|
not cv
|
1576
1578
|
or liname not in th_cvds
|
1577
1579
|
or cv.lower() not in th_cvds
|
1578
1580
|
or th_cvd.index(liname) < th_cvd.index(cv.lower())
|
1579
|
-
)
|
1580
|
-
|
1581
|
-
|
1581
|
+
):
|
1582
|
+
cv = iname
|
1583
|
+
elif not vcv and ext in VCV_EXTS and not iname.startswith("."):
|
1584
|
+
vcv = iname
|
1585
|
+
|
1586
|
+
if not cv:
|
1587
|
+
cv = vcv
|
1582
1588
|
|
1583
1589
|
if not self.args.no_dirsz:
|
1584
1590
|
tnf += len(files)
|
@@ -2812,7 +2818,7 @@ class Up2k(object):
|
|
2812
2818
|
# v5a -> v5b
|
2813
2819
|
# store rd+fn rather than warks to support nohash vols
|
2814
2820
|
try:
|
2815
|
-
cur.execute("select
|
2821
|
+
cur.execute("select c, w, rd, fn from iu limit 1").fetchone()
|
2816
2822
|
return
|
2817
2823
|
except:
|
2818
2824
|
pass
|
copyparty/util.py
CHANGED
@@ -2310,6 +2310,21 @@ def ujoin(rd , fn ) :
|
|
2310
2310
|
return rd or fn
|
2311
2311
|
|
2312
2312
|
|
2313
|
+
def str_anchor(txt) :
|
2314
|
+
if not txt:
|
2315
|
+
return 0, ""
|
2316
|
+
txt = txt.lower()
|
2317
|
+
a = txt.startswith("^")
|
2318
|
+
b = txt.endswith("$")
|
2319
|
+
if not b:
|
2320
|
+
if not a:
|
2321
|
+
return 1, txt # ~
|
2322
|
+
return 2, txt[1:] # ^
|
2323
|
+
if not a:
|
2324
|
+
return 3, txt[:-1] # $
|
2325
|
+
return 4, txt[1:-1] # ^$
|
2326
|
+
|
2327
|
+
|
2313
2328
|
def log_reloc(
|
2314
2329
|
log ,
|
2315
2330
|
re ,
|
copyparty/web/a/u2c.py
CHANGED
@@ -52,6 +52,7 @@ if PY2:
|
|
52
52
|
|
53
53
|
sys.dont_write_bytecode = True
|
54
54
|
bytes = str
|
55
|
+
files_decoder = lambda s: unicode(s, "utf8")
|
55
56
|
else:
|
56
57
|
from urllib.parse import quote_from_bytes as quote
|
57
58
|
from urllib.parse import unquote_to_bytes as unquote
|
@@ -61,6 +62,7 @@ else:
|
|
61
62
|
from queue import Queue
|
62
63
|
|
63
64
|
unicode = str
|
65
|
+
files_decoder = unicode
|
64
66
|
|
65
67
|
|
66
68
|
WTF8 = "replace" if PY2 else "surrogateescape"
|
@@ -1530,7 +1532,7 @@ source file/folder selection uses rsync syntax, meaning that:
|
|
1530
1532
|
""")
|
1531
1533
|
|
1532
1534
|
ap.add_argument("url", type=unicode, help="server url, including destination folder")
|
1533
|
-
ap.add_argument("files", type=
|
1535
|
+
ap.add_argument("files", type=files_decoder, nargs="+", help="files and/or folders to process")
|
1534
1536
|
ap.add_argument("-v", action="store_true", help="verbose")
|
1535
1537
|
ap.add_argument("-a", metavar="PASSWD", help="password or $filepath")
|
1536
1538
|
ap.add_argument("-s", action="store_true", help="file-search (disables upload)")
|
copyparty/web/browser.css.gz
CHANGED
Binary file
|
copyparty/web/browser.html
CHANGED
@@ -110,7 +110,7 @@
|
|
110
110
|
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td>
|
111
111
|
{%- if f.tags is defined %}
|
112
112
|
{%- for k in taglist %}<td>{{ f.tags[k]|e }}</td>{%- endfor %}
|
113
|
-
{%- endif %}<td>{{ f.ext
|
113
|
+
{%- endif %}<td>{{ f.ext }}</td><td>{{ f.dt }}</td></tr>
|
114
114
|
{%- endfor %}
|
115
115
|
|
116
116
|
</tbody>
|
copyparty/web/browser.js.gz
CHANGED
Binary file
|
copyparty/web/rups.html
CHANGED
copyparty/web/rups.js.gz
CHANGED
Binary file
|
copyparty/web/shares.html
CHANGED
copyparty/web/shares.js.gz
CHANGED
Binary file
|
copyparty/web/splash.js.gz
CHANGED
Binary file
|
copyparty/web/up2k.js.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.18.
|
3
|
+
Version: 1.18.10
|
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
|
@@ -37,6 +37,13 @@ Requires-Python: >=3.3
|
|
37
37
|
Description-Content-Type: text/markdown
|
38
38
|
License-File: LICENSE
|
39
39
|
Requires-Dist: Jinja2
|
40
|
+
Provides-Extra: all
|
41
|
+
Requires-Dist: argon2-cffi; extra == "all"
|
42
|
+
Requires-Dist: partftpy>=0.4.0; extra == "all"
|
43
|
+
Requires-Dist: Pillow; extra == "all"
|
44
|
+
Requires-Dist: pyftpdlib; extra == "all"
|
45
|
+
Requires-Dist: pyopenssl; extra == "all"
|
46
|
+
Requires-Dist: pyzmq; extra == "all"
|
40
47
|
Provides-Extra: thumbnails
|
41
48
|
Requires-Dist: Pillow; extra == "thumbnails"
|
42
49
|
Provides-Extra: thumbnails2
|
@@ -205,11 +212,14 @@ made in Norway 🇳🇴
|
|
205
212
|
|
206
213
|
just run **[copyparty-sfx.py](https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py)** -- that's it! 🎉
|
207
214
|
|
215
|
+
> ℹ️ the sfx is a [self-extractor](https://github.com/9001/copyparty/issues/270) which unpacks an embedded `tar.gz` into `$TEMP` -- if this looks too scary, you can use the [zipapp](#zipapp) which has slightly worse performance
|
216
|
+
|
208
217
|
* or install through [pypi](https://pypi.org/project/copyparty/): `python3 -m pip install --user -U copyparty`
|
209
218
|
* or if you cannot install python, you can use [copyparty.exe](#copypartyexe) instead
|
210
219
|
* or install [on arch](#arch-package) ╱ [on NixOS](#nixos-module) ╱ [through nix](#nix-package)
|
211
220
|
* or if you are on android, [install copyparty in termux](#install-on-android)
|
212
221
|
* or maybe you have a [synology nas / dsm](./docs/synology-dsm.md)
|
222
|
+
* or if you have [uv](https://docs.astral.sh/uv/) installed, run `uv tool run copyparty`
|
213
223
|
* or if your computer is messed up and nothing else works, [try the pyz](#zipapp)
|
214
224
|
* or if your OS is dead, give the [bootable flashdrive / cd-rom](https://a.ocv.me/pub/stuff/edcd001/enterprise-edition/) a spin
|
215
225
|
* or if you don't trust copyparty yet and want to isolate it a little, then...
|
@@ -570,12 +580,17 @@ anyone trying to bruteforce a password gets banned according to `--ban-pw`; defa
|
|
570
580
|
|
571
581
|
and if you want to use config files instead of commandline args (good!) then here's the same examples as a configfile; save it as `foobar.conf` and use it like this: `python copyparty-sfx.py -c foobar.conf`
|
572
582
|
|
583
|
+
* you can also `PRTY_CONFIG=foobar.conf python copyparty-sfx.py` (convenient in docker etc)
|
584
|
+
|
573
585
|
```yaml
|
574
586
|
[accounts]
|
575
587
|
u1: p1 # create account "u1" with password "p1"
|
576
588
|
u2: p2 # (note that comments must have
|
577
589
|
u3: p3 # two spaces before the # sign)
|
578
590
|
|
591
|
+
[groups]
|
592
|
+
g1: u1, u2 # create a group
|
593
|
+
|
579
594
|
[/] # this URL will be mapped to...
|
580
595
|
/srv # ...this folder on the server filesystem
|
581
596
|
accs:
|
@@ -585,6 +600,7 @@ and if you want to use config files instead of commandline args (good!) then her
|
|
585
600
|
/mnt/music # which is mapped to this folder
|
586
601
|
accs:
|
587
602
|
r: u1, u2 # only these accounts can read,
|
603
|
+
r: @g1 # (exactly the same, just with a group instead)
|
588
604
|
rw: u3 # and only u3 can read-write
|
589
605
|
|
590
606
|
[/inc]
|
@@ -1141,6 +1157,9 @@ open the `[🎺]` media-player-settings tab to configure it,
|
|
1141
1157
|
* `[awo]` is `opus` in a `weba` file, good for iPhones (iOS 17.5 and newer) but Apple is still fixing some state-confusion bugs as of iOS 18.2.1
|
1142
1158
|
* `[caf]` is `opus` in a `caf` file, good for iPhones (iOS 11 through 17), technically unsupported by Apple but works for the most part
|
1143
1159
|
* `[mp3]` -- the myth, the legend, the undying master of mediocre sound quality that definitely works everywhere
|
1160
|
+
* `[flac]` -- lossless but compressed, for LAN and/or fiber playback on electrostatic headphones
|
1161
|
+
* `[wav]` -- lossless and uncompressed, for LAN and/or fiber playback on electrostatic headphones connected to very old equipment
|
1162
|
+
* `flac` and `wav` must be enabled with `--allow-flac` / `--allow-wav` to allow spending the disk space
|
1144
1163
|
* "tint" reduces the contrast of the playback bar
|
1145
1164
|
|
1146
1165
|
|
@@ -1482,6 +1501,8 @@ note that this disables hotlinking because the opengraph spec demands it; to sne
|
|
1482
1501
|
|
1483
1502
|
you can also hotlink files regardless by appending `?raw` to the url
|
1484
1503
|
|
1504
|
+
> WARNING: if you plan to use WebDAV, then `--og-ua` / `og_ua` must be configured
|
1505
|
+
|
1485
1506
|
if you want to entirely replace the copyparty response with your own jinja2 template, give the template filepath to `--og-tpl` or volflag `og_tpl` (all members of `HttpCli` are available through the `this` object)
|
1486
1507
|
|
1487
1508
|
|
@@ -1938,6 +1959,8 @@ you can disable the built-in password-based login system, and instead replace it
|
|
1938
1959
|
|
1939
1960
|
* the regular config-defined users will be used as a fallback for requests which don't include a valid (trusted) IdP username header
|
1940
1961
|
|
1962
|
+
* if your IdP-server is slow, consider `--idp-cookie` and let requests with the cookie `cppws` bypass the IdP; experimental sessions-based feature added for a party
|
1963
|
+
|
1941
1964
|
some popular identity providers are [Authelia](https://www.authelia.com/) (config-file based) and [authentik](https://goauthentik.io/) (GUI-based, more complex)
|
1942
1965
|
|
1943
1966
|
there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik) which is hopefully a good starting point (alternatively see [./docs/idp.md](./docs/idp.md) if you're the DIY type)
|
@@ -2486,6 +2509,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
|
|
2486
2509
|
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
|
2487
2510
|
| **sony psp** 5.50 | can browse, upload/mkdir/msg (thx dwarf) [screenshot](https://github.com/user-attachments/assets/9d21f020-1110-4652-abeb-6fc09c533d4f) |
|
2488
2511
|
| **nintendo 3ds** | can browse, upload, view thumbnails (thx bnjmn) |
|
2512
|
+
| **Nintendo Wii (Opera 9.0 "Internet Channel")** | can browse, can't upload or download (no local storage), can view images - works best with `?b=u`, default view broken |
|
2489
2513
|
|
2490
2514
|
<p align="center"><img src="https://github.com/user-attachments/assets/88deab3d-6cad-4017-8841-2f041472b853" /></p>
|
2491
2515
|
|
@@ -2756,6 +2780,10 @@ optionally also specify `--ah-cli` to enter an interactive mode where it will ha
|
|
2756
2780
|
|
2757
2781
|
the default configs take about 0.4 sec and 256 MiB RAM to process a new password on a decent laptop
|
2758
2782
|
|
2783
|
+
when generating hashes using `--ah-cli` for docker or systemd services, make sure it is using the same `--ah-salt` by:
|
2784
|
+
* inspecting the generated salt using `--show-ah-salt` in copyparty service configuration
|
2785
|
+
* setting the same `--ah-salt` in both environments
|
2786
|
+
|
2759
2787
|
|
2760
2788
|
## https
|
2761
2789
|
|
@@ -2900,7 +2928,7 @@ then again, if you are already into downloading shady binaries from the internet
|
|
2900
2928
|
|
2901
2929
|
## zipapp
|
2902
2930
|
|
2903
|
-
another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but it does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
|
2931
|
+
another emergency alternative, [copyparty.pyz](https://github.com/9001/copyparty/releases/latest/download/copyparty.pyz) has less features, is slow, requires python 3.7 or newer, worse compression, and more importantly is unable to benefit from more recent versions of jinja2 and such (which makes it less secure)... lots of drawbacks with this one really -- but, unlike the sfx, it is a completely normal zipfile which does not unpack any temporary files to disk, so it *may* just work if the regular sfx fails to start because the computer is messed up in certain funky ways, so it's worth a shot if all else fails
|
2904
2932
|
|
2905
2933
|
run it by doubleclicking it, or try typing `python copyparty.pyz` in your terminal/console/commandline/telex if that fails
|
2906
2934
|
|
@@ -1,7 +1,7 @@
|
|
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=SJtQjM-9PP9K-IaoM9M3iNKvRApp0omOrAN6YtXTPNM,2599
|
2
|
+
copyparty/__main__.py,sha256=F9x45nQDLPe4s-QHMQhWhnZsdOqHZ8FuJrs-xN7WKvg,128395
|
3
|
+
copyparty/__version__.py,sha256=efITsXVBc-tiLxawt-9axHNIw551PrdvusgzacjpmRw,249
|
4
|
+
copyparty/authsrv.py,sha256=7DGDYZseSe7ghT9FY5M5dSQaYtkcRQs7Lv5fX0vvPRg,122824
|
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
|
@@ -11,28 +11,28 @@ copyparty/cfg.py,sha256=THceFupFmsZWF8iJKDDHR_XUEU3TNQgDRJB50iLiC1k,15734
|
|
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=S0w6iMR8AlzLc_Aqn-TKuUJ-vNbmeQF6SQs614-NFOE,18107
|
14
|
-
copyparty/httpcli.py,sha256=
|
14
|
+
copyparty/httpcli.py,sha256=HMEWwIgfSxhNVKLrZeGVa6lqXblDciW7UxiG2tppaYA,232884
|
15
15
|
copyparty/httpconn.py,sha256=IA9fdCjigawZ4kWhgvVN3nSiy5pb3W2qaE6rFqUYdq0,6943
|
16
|
-
copyparty/httpsrv.py,sha256=
|
16
|
+
copyparty/httpsrv.py,sha256=Qyhna6GTIHnmpUBwUicdtDrXGwct8oKap0LqEtGD5X4,18911
|
17
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
|
-
copyparty/mtag.py,sha256=
|
20
|
+
copyparty/mtag.py,sha256=uAHixYCzB52dN9rhmRV_As_YX_baL5Ha-oRnTtmDdcg,20053
|
21
21
|
copyparty/multicast.py,sha256=Me4XEEJijvvK2lMRwmGU2hsaI5_E9AEpCjIC4b9UefA,12393
|
22
22
|
copyparty/pwhash.py,sha256=zHoz9FHGkFBxoRvSfG1XyjN3ibww_h5GE6_m5yS-fws,4246
|
23
23
|
copyparty/smbd.py,sha256=Czo8SRkkl4ndCwEUe9Cbr8v0YOnyQHzubGSguPizuTc,14651
|
24
24
|
copyparty/ssdp.py,sha256=R1Z61GZOxBMF2Sk4RTxKWMOemogmcjEWG-CvLihd45k,7023
|
25
25
|
copyparty/star.py,sha256=tV5BbX6AiQ7N4UU8DYtSTckNYeoeey4DBqq4LjfymbY,3818
|
26
26
|
copyparty/sutil.py,sha256=E65jAaOzHlJYnqsOKDMPVT8kALPUVexpkSStWBdItkY,3231
|
27
|
-
copyparty/svchub.py,sha256=
|
27
|
+
copyparty/svchub.py,sha256=lAa-4HfD1LSv3Sn3pOtgLWGHyPhtGDXSCzGvpVB21E0,49140
|
28
28
|
copyparty/szip.py,sha256=9srQzjsTBrBadf6QMh4YRAL70rkZLevAOHqXWK5jvr8,8846
|
29
|
-
copyparty/tcpsrv.py,sha256=
|
29
|
+
copyparty/tcpsrv.py,sha256=AND0QFPs8xD5-Jzr-_KcOa2dYx5AdZPboKo_M0cuh5g,20478
|
30
30
|
copyparty/tftpd.py,sha256=QuPcdx77gLmEpit3lLc0x4Px6BrBBKJpJl4VqINc5O8,14254
|
31
|
-
copyparty/th_cli.py,sha256=
|
32
|
-
copyparty/th_srv.py,sha256=
|
31
|
+
copyparty/th_cli.py,sha256=1tq5yFTSa6ppy9xXuxUsm1SqF7Ps69b0cm4gAGM0vVo,5460
|
32
|
+
copyparty/th_srv.py,sha256=0WyPJRhKhCphGBe3gC53yNrlu0IT7WQ6WaWVSGxzpog,34749
|
33
33
|
copyparty/u2idx.py,sha256=4Y5OOPyVkc-pS0z6e3p4StXAMnjHobSOMmMsvNUTD34,13674
|
34
|
-
copyparty/up2k.py,sha256=
|
35
|
-
copyparty/util.py,sha256=
|
34
|
+
copyparty/up2k.py,sha256=lN6jB_hv-dbdDwvm61MLKFTjazLZbQ-buXmh5ANi4UY,179456
|
35
|
+
copyparty/util.py,sha256=XQN86nmxN4RgWWw4k1dIbWeIAR_Dqh_7Z4X9WobwRFI,105732
|
36
36
|
copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
37
|
copyparty/bos/bos.py,sha256=DYt5mJJNt-935rU7HRm8kt_whpcVSI0uSphvD7PXrJo,2247
|
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=MxRofvhXjmUN7RtXtC17_9AlROVNUT-66WwJ_pHLY9c,8258
|
58
|
-
copyparty/web/browser.css.gz,sha256=
|
59
|
-
copyparty/web/browser.html,sha256=
|
60
|
-
copyparty/web/browser.js.gz,sha256=
|
58
|
+
copyparty/web/browser.css.gz,sha256=Q4tBdkN0TTjrc-NTKrJy3RUoaeh7mJOe7_a95kGKf9M,11767
|
59
|
+
copyparty/web/browser.html,sha256=lhelkXI8_HGfuqo_5b6XEGzf8VNodOMXE9kbv31JtbM,4790
|
60
|
+
copyparty/web/browser.js.gz,sha256=02kCf8mpq2N7R6Xcl7RVRqLdMepu2tdNhdOglXAOZog,199166
|
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
|
@@ -73,29 +73,24 @@ copyparty/web/mde.js.gz,sha256=kN2eUSvr4mFuksfK4-4LimJmWdwsao39Sea2lWtu8L0,2224
|
|
73
73
|
copyparty/web/msg.css.gz,sha256=u90fXYAVrMD-jqwf5XFVC1ptSpSHZUe8Mez6PX101P8,300
|
74
74
|
copyparty/web/msg.html,sha256=w9CM3hkLLGJX9fWEaG4gSbTOPe2GcPqW8BpSCDiFzOI,977
|
75
75
|
copyparty/web/rups.css.gz,sha256=pWklsym27oGGr-8tYQR7WnZvGZElAgCwLzlwTDErNAM,647
|
76
|
-
copyparty/web/rups.html,sha256=
|
77
|
-
copyparty/web/rups.js.gz,sha256=
|
76
|
+
copyparty/web/rups.html,sha256=36UfDfHYMltw7_qAWGUGeRF8zaCSw2EjclhsymW94n0,1512
|
77
|
+
copyparty/web/rups.js.gz,sha256=99RaGb6fhpER4_eNyauN7yOhA8NFcO4qNKazlnyrano,854
|
78
78
|
copyparty/web/shares.css.gz,sha256=SdPlZCBwz9tkPkgEo5pSPDOZSI079njxEfkJ64-iW3c,547
|
79
|
-
copyparty/web/shares.html,sha256=
|
80
|
-
copyparty/web/shares.js.gz,sha256=
|
79
|
+
copyparty/web/shares.html,sha256=ZZ9BIuzhbVtJCAZOb_PAaEY_z9jo8i93QEJolNDHX3g,2578
|
80
|
+
copyparty/web/shares.js.gz,sha256=KEOx1OxQeEQNHNjsPXCvtWGFIRQSKy_tP7XSvreNCXk,966
|
81
81
|
copyparty/web/splash.css.gz,sha256=S8_A7JJl71xACRBYGzafeaD82OacW6Fa7oKPiNyrhAs,1087
|
82
82
|
copyparty/web/splash.html,sha256=0MvDe1lKfGqczi7d4nKjWjG0cRVyvs8J6sDEj3DCPSI,6376
|
83
|
-
copyparty/web/splash.js.gz,sha256=
|
83
|
+
copyparty/web/splash.js.gz,sha256=FyuMJyupQ-aLk9gemy8U0R6XT83hMRtarvPcKkmjoY0,7766
|
84
84
|
copyparty/web/svcs.html,sha256=mamJdq0hsmHqG2BQsf9jg8G9bAl338wUhUZ2WtXOlGQ,14865
|
85
85
|
copyparty/web/svcs.js.gz,sha256=AYatNKyT_bKRWX8sb3WD_iujBY3L4P7HWBrsuMctsLs,722
|
86
86
|
copyparty/web/ui.css.gz,sha256=e3iIflzddmjoyPrun_1jsu9j7fbdonNQLyhEE2oKKOQ,2819
|
87
|
-
copyparty/web/up2k.js.gz,sha256=
|
88
|
-
copyparty/web/util.js.gz,sha256=
|
87
|
+
copyparty/web/up2k.js.gz,sha256=aoq6uXrL77zlYQnx9y9VNUVL7YBOHc4u-zFlIrQSX14,24811
|
88
|
+
copyparty/web/util.js.gz,sha256=IpbiLt7QS9o-Yzj_07nwz3SghQWxX69TN5cH-AIhDKU,15512
|
89
89
|
copyparty/web/w.hash.js.gz,sha256=cFH6Xo4YRgH9Wr7RmHMSEfpuTmmIvEmzmSvv4RLmyPU,1193
|
90
90
|
copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
91
91
|
copyparty/web/a/partyfuse.py,sha256=9p5Hpg_IBiSimv7j9kmPhCGpy-FLXSRUOYnLjJ5JifU,28049
|
92
|
-
copyparty/web/a/u2c.py,sha256=
|
92
|
+
copyparty/web/a/u2c.py,sha256=f_KR1ZhOjJYBnyYlJbBXY-TnNITeT7HOf0R3pR_9DIM,53248
|
93
93
|
copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
|
94
|
-
copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
|
95
|
-
copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
|
96
|
-
copyparty/web/dd/4.png,sha256=fIwEVmtZNZtloZuVEKPKnkx3SELwRJmB3US61y7t2lI,248
|
97
|
-
copyparty/web/dd/5.png,sha256=Lfpu8-yOlhONuoMbygloKqQVPXSm9gjxH2gUYn5QQAE,250
|
98
|
-
copyparty/web/dd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
99
94
|
copyparty/web/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
100
95
|
copyparty/web/deps/busy.mp3.gz,sha256=EVphk1_HYyRKJmtpeK99vbAstF7ub1f9ndu020H8PQ8,106
|
101
96
|
copyparty/web/deps/easymde.css.gz,sha256=vWxfueI64rPikuqFj69wJBtGisqf93AheQtOZqgUI_c,3041
|
@@ -110,9 +105,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
|
|
110
105
|
copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
|
111
106
|
copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
|
112
107
|
copyparty/web/deps/sha512.hw.js.gz,sha256=UAed2_ocklZCnIzcSYz2h4P1ycztlCLj-ewsRTud2lU,7939
|
113
|
-
copyparty-1.18.
|
114
|
-
copyparty-1.18.
|
115
|
-
copyparty-1.18.
|
116
|
-
copyparty-1.18.
|
117
|
-
copyparty-1.18.
|
118
|
-
copyparty-1.18.
|
108
|
+
copyparty-1.18.10.dist-info/licenses/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
|
109
|
+
copyparty-1.18.10.dist-info/METADATA,sha256=nM2VOQmUUXuiEFQ-Yqkzs-Y4jDYJY4FJS0NOeTEnvyg,169618
|
110
|
+
copyparty-1.18.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
111
|
+
copyparty-1.18.10.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
|
112
|
+
copyparty-1.18.10.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
|
113
|
+
copyparty-1.18.10.dist-info/RECORD,,
|
copyparty/web/dd/2.png
DELETED
Binary file
|
copyparty/web/dd/3.png
DELETED
Binary file
|
copyparty/web/dd/4.png
DELETED
Binary file
|
copyparty/web/dd/5.png
DELETED
Binary file
|
copyparty/web/dd/__init__.py
DELETED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|