copyparty 1.15.8__py3-none-any.whl → 1.15.9__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 +10 -1
- copyparty/__version__.py +2 -2
- copyparty/authsrv.py +14 -3
- copyparty/cfg.py +1 -0
- copyparty/httpcli.py +147 -4
- copyparty/metrics.py +1 -1
- copyparty/u2idx.py +5 -3
- copyparty/up2k.py +1 -0
- copyparty/util.py +15 -7
- copyparty/web/a/u2c.py +13 -9
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.15.8.dist-info → copyparty-1.15.9.dist-info}/METADATA +26 -1
- {copyparty-1.15.8.dist-info → copyparty-1.15.9.dist-info}/RECORD +18 -18
- {copyparty-1.15.8.dist-info → copyparty-1.15.9.dist-info}/LICENSE +0 -0
- {copyparty-1.15.8.dist-info → copyparty-1.15.9.dist-info}/WHEEL +0 -0
- {copyparty-1.15.8.dist-info → copyparty-1.15.9.dist-info}/entry_points.txt +0 -0
- {copyparty-1.15.8.dist-info → copyparty-1.15.9.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -1029,7 +1029,7 @@ def add_network(ap):
|
|
1029
1029
|
else:
|
1030
1030
|
ap2.add_argument("--freebind", action="store_true", help="allow listening on IPs which do not yet exist, for example if the network interfaces haven't finished going up. Only makes sense for IPs other than '0.0.0.0', '127.0.0.1', '::', and '::1'. May require running as root (unless net.ipv6.ip_nonlocal_bind)")
|
1031
1031
|
ap2.add_argument("--s-thead", metavar="SEC", type=int, default=120, help="socket timeout (read request header)")
|
1032
|
-
ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=
|
1032
|
+
ap2.add_argument("--s-tbody", metavar="SEC", type=float, default=128.0, help="socket timeout (read/write request/response bodies). Use 60 on fast servers (default is extremely safe). Disable with 0 if reverse-proxied for a 2%% speed boost")
|
1033
1033
|
ap2.add_argument("--s-rd-sz", metavar="B", type=int, default=256*1024, help="socket read size in bytes (indirectly affects filesystem writes; recommendation: keep equal-to or lower-than \033[33m--iobuf\033[0m)")
|
1034
1034
|
ap2.add_argument("--s-wr-sz", metavar="B", type=int, default=256*1024, help="socket write size in bytes")
|
1035
1035
|
ap2.add_argument("--s-wr-slp", metavar="SEC", type=float, default=0.0, help="debug: socket write delay in seconds")
|
@@ -1349,6 +1349,14 @@ def add_transcoding(ap):
|
|
1349
1349
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after \033[33mSEC\033[0m seconds")
|
1350
1350
|
|
1351
1351
|
|
1352
|
+
def add_rss(ap):
|
1353
|
+
ap2 = ap.add_argument_group('RSS options')
|
1354
|
+
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental)")
|
1355
|
+
ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
|
1356
|
+
ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
|
1357
|
+
ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files")
|
1358
|
+
|
1359
|
+
|
1352
1360
|
def add_db_general(ap, hcores):
|
1353
1361
|
noidx = APPLESAN_TXT if MACOS else ""
|
1354
1362
|
ap2 = ap.add_argument_group('general db options')
|
@@ -1518,6 +1526,7 @@ def run_argparse(
|
|
1518
1526
|
add_db_metadata(ap)
|
1519
1527
|
add_thumbnail(ap)
|
1520
1528
|
add_transcoding(ap)
|
1529
|
+
add_rss(ap)
|
1521
1530
|
add_ftp(ap)
|
1522
1531
|
add_webdav(ap)
|
1523
1532
|
add_tftp(ap)
|
copyparty/__version__.py
CHANGED
copyparty/authsrv.py
CHANGED
@@ -158,8 +158,11 @@ class Lim(object):
|
|
158
158
|
self.chk_rem(rem)
|
159
159
|
if sz != -1:
|
160
160
|
self.chk_sz(sz)
|
161
|
-
|
162
|
-
|
161
|
+
else:
|
162
|
+
sz = 0
|
163
|
+
|
164
|
+
self.chk_vsz(broker, ptop, sz, volgetter)
|
165
|
+
self.chk_df(abspath, sz) # side effects; keep last-ish
|
163
166
|
|
164
167
|
ap2, vp2 = self.rot(abspath)
|
165
168
|
if abspath == ap2:
|
@@ -199,7 +202,15 @@ class Lim(object):
|
|
199
202
|
|
200
203
|
if self.dft < time.time():
|
201
204
|
self.dft = int(time.time()) + 300
|
202
|
-
|
205
|
+
|
206
|
+
df, du, err = get_df(abspath, True)
|
207
|
+
if err:
|
208
|
+
t = "failed to read disk space usage for [%s]: %s"
|
209
|
+
self.log(t % (abspath, err), 3)
|
210
|
+
self.dfv = 0xAAAAAAAAA # 42.6 GiB
|
211
|
+
else:
|
212
|
+
self.dfv = df or 0
|
213
|
+
|
203
214
|
for j in list(self.reg.values()) if self.reg else []:
|
204
215
|
self.dfv -= int(j["size"] / (len(j["hash"]) or 999) * len(j["need"]))
|
205
216
|
|
copyparty/cfg.py
CHANGED
copyparty/httpcli.py
CHANGED
@@ -127,6 +127,8 @@ LOGUES = [[0, ".prologue.html"], [1, ".epilogue.html"]]
|
|
127
127
|
|
128
128
|
READMES = [[0, ["preadme.md", "PREADME.md"]], [1, ["readme.md", "README.md"]]]
|
129
129
|
|
130
|
+
RSS_SORT = {"m": "mt", "u": "at", "n": "fn", "s": "sz"}
|
131
|
+
|
130
132
|
|
131
133
|
class HttpCli(object):
|
132
134
|
"""
|
@@ -1196,8 +1198,146 @@ class HttpCli(object):
|
|
1196
1198
|
if "h" in self.uparam:
|
1197
1199
|
return self.tx_mounts()
|
1198
1200
|
|
1201
|
+
if "rss" in self.uparam:
|
1202
|
+
return self.tx_rss()
|
1203
|
+
|
1199
1204
|
return self.tx_browser()
|
1200
1205
|
|
1206
|
+
def tx_rss(self) :
|
1207
|
+
if self.do_log:
|
1208
|
+
self.log("RSS %s @%s" % (self.req, self.uname))
|
1209
|
+
|
1210
|
+
if not self.can_read:
|
1211
|
+
return self.tx_404()
|
1212
|
+
|
1213
|
+
vn = self.vn
|
1214
|
+
if not vn.flags.get("rss"):
|
1215
|
+
raise Pebkac(405, "RSS is disabled in server config")
|
1216
|
+
|
1217
|
+
rem = self.rem
|
1218
|
+
idx = self.conn.get_u2idx()
|
1219
|
+
if not idx or not hasattr(idx, "p_end"):
|
1220
|
+
if not HAVE_SQLITE3:
|
1221
|
+
raise Pebkac(500, "sqlite3 not found on server; rss is disabled")
|
1222
|
+
raise Pebkac(500, "server busy, cannot generate rss; please retry in a bit")
|
1223
|
+
|
1224
|
+
uv = [rem]
|
1225
|
+
if "recursive" in self.uparam:
|
1226
|
+
uq = "up.rd like ?||'%'"
|
1227
|
+
else:
|
1228
|
+
uq = "up.rd == ?"
|
1229
|
+
|
1230
|
+
zs = str(self.uparam.get("fext", self.args.rss_fext))
|
1231
|
+
if zs in ("True", "False"):
|
1232
|
+
zs = ""
|
1233
|
+
if zs:
|
1234
|
+
zsl = []
|
1235
|
+
for ext in zs.split(","):
|
1236
|
+
zsl.append("+up.fn like '%.'||?")
|
1237
|
+
uv.append(ext)
|
1238
|
+
uq += " and ( %s )" % (" or ".join(zsl),)
|
1239
|
+
|
1240
|
+
zs1 = self.uparam.get("sort", self.args.rss_sort)
|
1241
|
+
zs2 = zs1.lower()
|
1242
|
+
zs = RSS_SORT.get(zs2)
|
1243
|
+
if not zs:
|
1244
|
+
raise Pebkac(400, "invalid sort key; must be m/u/n/s")
|
1245
|
+
|
1246
|
+
uq += " order by up." + zs
|
1247
|
+
if zs1 == zs2:
|
1248
|
+
uq += " desc"
|
1249
|
+
|
1250
|
+
nmax = int(self.uparam.get("nf") or self.args.rss_nf)
|
1251
|
+
|
1252
|
+
hits = idx.run_query(self.uname, [self.vn], uq, uv, False, False, nmax)[0]
|
1253
|
+
|
1254
|
+
pw = self.ouparam.get("pw")
|
1255
|
+
if pw:
|
1256
|
+
q_pw = "?pw=%s" % (pw,)
|
1257
|
+
a_pw = "&pw=%s" % (pw,)
|
1258
|
+
for i in hits:
|
1259
|
+
i["rp"] += a_pw if "?" in i["rp"] else q_pw
|
1260
|
+
else:
|
1261
|
+
q_pw = a_pw = ""
|
1262
|
+
|
1263
|
+
title = self.uparam.get("title") or self.vpath.split("/")[-1]
|
1264
|
+
etitle = html_escape(title, True, True)
|
1265
|
+
|
1266
|
+
baseurl = "%s://%s%s" % (
|
1267
|
+
"https" if self.is_https else "http",
|
1268
|
+
self.host,
|
1269
|
+
self.args.SRS,
|
1270
|
+
)
|
1271
|
+
feed = "%s%s" % (baseurl, self.req[1:])
|
1272
|
+
efeed = html_escape(feed, True, True)
|
1273
|
+
edirlink = efeed.split("?")[0] + q_pw
|
1274
|
+
|
1275
|
+
ret = [
|
1276
|
+
"""\
|
1277
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
1278
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
1279
|
+
\t<channel>
|
1280
|
+
\t\t<atom:link href="%s" rel="self" type="application/rss+xml" />
|
1281
|
+
\t\t<title>%s</title>
|
1282
|
+
\t\t<description></description>
|
1283
|
+
\t\t<link>%s</link>
|
1284
|
+
\t\t<generator>copyparty-1</generator>
|
1285
|
+
"""
|
1286
|
+
% (efeed, etitle, edirlink)
|
1287
|
+
]
|
1288
|
+
|
1289
|
+
q = "select fn from cv where rd=? and dn=?"
|
1290
|
+
crd, cdn = rem.rsplit("/", 1) if "/" in rem else ("", rem)
|
1291
|
+
try:
|
1292
|
+
cfn = idx.cur[self.vn.realpath].execute(q, (crd, cdn)).fetchone()[0]
|
1293
|
+
bos.stat(os.path.join(vn.canonical(rem), cfn))
|
1294
|
+
cv_url = "%s%s?th=jf%s" % (baseurl, vjoin(self.vpath, cfn), a_pw)
|
1295
|
+
cv_url = html_escape(cv_url, True, True)
|
1296
|
+
zs = """\
|
1297
|
+
\t\t<image>
|
1298
|
+
\t\t\t<url>%s</url>
|
1299
|
+
\t\t\t<title>%s</title>
|
1300
|
+
\t\t\t<link>%s</link>
|
1301
|
+
\t\t</image>
|
1302
|
+
"""
|
1303
|
+
ret.append(zs % (cv_url, etitle, edirlink))
|
1304
|
+
except:
|
1305
|
+
pass
|
1306
|
+
|
1307
|
+
for i in hits:
|
1308
|
+
iurl = html_escape("%s%s" % (baseurl, i["rp"]), True, True)
|
1309
|
+
title = unquotep(i["rp"].split("?")[0].split("/")[-1])
|
1310
|
+
title = html_escape(title, True, True)
|
1311
|
+
tag_t = str(i["tags"].get("title") or "")
|
1312
|
+
tag_a = str(i["tags"].get("artist") or "")
|
1313
|
+
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
|
1314
|
+
desc = html_escape(desc, True, True) if desc else title
|
1315
|
+
mime = html_escape(guess_mime(title))
|
1316
|
+
lmod = formatdate(i["ts"])
|
1317
|
+
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
|
1318
|
+
zs = (
|
1319
|
+
"""\
|
1320
|
+
\t\t<item>
|
1321
|
+
\t\t\t<guid>%s</guid>
|
1322
|
+
\t\t\t<link>%s</link>
|
1323
|
+
\t\t\t<title>%s</title>
|
1324
|
+
\t\t\t<description>%s</description>
|
1325
|
+
\t\t\t<pubDate>%s</pubDate>
|
1326
|
+
\t\t\t<enclosure url="%s" type="%s" length="%d"/>
|
1327
|
+
"""
|
1328
|
+
% zsa
|
1329
|
+
)
|
1330
|
+
dur = i["tags"].get(".dur")
|
1331
|
+
if dur:
|
1332
|
+
zs += "\t\t\t<itunes:duration>%d</itunes:duration>\n" % (dur,)
|
1333
|
+
ret.append(zs + "\t\t</item>\n")
|
1334
|
+
|
1335
|
+
ret.append("\t</channel>\n</rss>\n")
|
1336
|
+
bret = "".join(ret).encode("utf-8", "replace")
|
1337
|
+
self.reply(bret, 200, "text/xml; charset=utf-8")
|
1338
|
+
self.log("rss: %d hits, %d bytes" % (len(hits), len(bret)))
|
1339
|
+
return True
|
1340
|
+
|
1201
1341
|
def handle_propfind(self) :
|
1202
1342
|
if self.do_log:
|
1203
1343
|
self.log("PFIND %s @%s" % (self.req, self.uname))
|
@@ -2481,6 +2621,7 @@ class HttpCli(object):
|
|
2481
2621
|
except:
|
2482
2622
|
# maybe busted handle (eg. disk went full)
|
2483
2623
|
f.close()
|
2624
|
+
chashes = [] # exception flag
|
2484
2625
|
raise
|
2485
2626
|
finally:
|
2486
2627
|
if locked:
|
@@ -2489,9 +2630,11 @@ class HttpCli(object):
|
|
2489
2630
|
num_left, t = x.get()
|
2490
2631
|
if num_left < 0:
|
2491
2632
|
self.loud_reply(t, status=500)
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
2633
|
+
if chashes: # kills exception bubbling otherwise
|
2634
|
+
return False
|
2635
|
+
else:
|
2636
|
+
t = "got %d more chunks, %d left"
|
2637
|
+
self.log(t % (len(written), num_left), 6)
|
2495
2638
|
|
2496
2639
|
if num_left < 0:
|
2497
2640
|
raise Pebkac(500, "unconfirmed; see serverlog")
|
@@ -5082,7 +5225,7 @@ class HttpCli(object):
|
|
5082
5225
|
self.log("#wow #whoa")
|
5083
5226
|
|
5084
5227
|
if not self.args.nid:
|
5085
|
-
free, total = get_df(abspath)
|
5228
|
+
free, total, _ = get_df(abspath, False)
|
5086
5229
|
if total is not None:
|
5087
5230
|
h1 = humansize(free or 0)
|
5088
5231
|
h2 = humansize(total)
|
copyparty/metrics.py
CHANGED
@@ -128,7 +128,7 @@ class Metrics(object):
|
|
128
128
|
addbh("cpp_disk_size_bytes", "total HDD size of volume")
|
129
129
|
addbh("cpp_disk_free_bytes", "free HDD space in volume")
|
130
130
|
for vpath, vol in allvols:
|
131
|
-
free, total = get_df(vol.realpath)
|
131
|
+
free, total, _ = get_df(vol.realpath, False)
|
132
132
|
if free is None or total is None:
|
133
133
|
continue
|
134
134
|
|
copyparty/u2idx.py
CHANGED
@@ -91,7 +91,7 @@ class U2idx(object):
|
|
91
91
|
uv = [wark[:16], wark]
|
92
92
|
|
93
93
|
try:
|
94
|
-
return self.run_query(uname, vols, uq, uv, False, 99999)[0]
|
94
|
+
return self.run_query(uname, vols, uq, uv, False, True, 99999)[0]
|
95
95
|
except:
|
96
96
|
raise Pebkac(500, min_ex())
|
97
97
|
|
@@ -295,7 +295,7 @@ class U2idx(object):
|
|
295
295
|
q += " lower({}) {} ? ) ".format(field, oper)
|
296
296
|
|
297
297
|
try:
|
298
|
-
return self.run_query(uname, vols, q, va, have_mt, lim)
|
298
|
+
return self.run_query(uname, vols, q, va, have_mt, True, lim)
|
299
299
|
except Exception as ex:
|
300
300
|
raise Pebkac(500, repr(ex))
|
301
301
|
|
@@ -306,6 +306,7 @@ class U2idx(object):
|
|
306
306
|
uq ,
|
307
307
|
uv ,
|
308
308
|
have_mt ,
|
309
|
+
sort ,
|
309
310
|
lim ,
|
310
311
|
) :
|
311
312
|
if self.args.srch_dbg:
|
@@ -452,7 +453,8 @@ class U2idx(object):
|
|
452
453
|
done_flag.append(True)
|
453
454
|
self.active_id = ""
|
454
455
|
|
455
|
-
|
456
|
+
if sort:
|
457
|
+
ret.sort(key=itemgetter("rp"))
|
456
458
|
|
457
459
|
return ret, list(taglist.keys()), lim < 0 and not clamped
|
458
460
|
|
copyparty/up2k.py
CHANGED
@@ -3481,6 +3481,7 @@ class Up2k(object):
|
|
3481
3481
|
for chash in written:
|
3482
3482
|
job["need"].remove(chash)
|
3483
3483
|
except Exception as ex:
|
3484
|
+
# dead tcp connections can get here by timeout (OK)
|
3484
3485
|
return -2, "confirm_chunk, chash(%s) %r" % (chash, ex) # type: ignore
|
3485
3486
|
|
3486
3487
|
ret = len(job["need"])
|
copyparty/util.py
CHANGED
@@ -192,6 +192,9 @@ except:
|
|
192
192
|
ansi_re = re.compile("\033\\[[^mK]*[mK]")
|
193
193
|
|
194
194
|
|
195
|
+
BOS_SEP = ("%s" % (os.sep,)).encode("ascii")
|
196
|
+
|
197
|
+
|
195
198
|
surrogateescape.register_surrogateescape()
|
196
199
|
if WINDOWS and PY2:
|
197
200
|
FS_ENCODING = "utf-8"
|
@@ -2407,22 +2410,27 @@ def wunlink(log , abspath , flags ) :
|
|
2407
2410
|
return _fs_mvrm(log, abspath, "", False, flags)
|
2408
2411
|
|
2409
2412
|
|
2410
|
-
def get_df(abspath )
|
2413
|
+
def get_df(abspath , prune ) :
|
2411
2414
|
try:
|
2412
|
-
|
2415
|
+
ap = fsenc(abspath)
|
2416
|
+
while prune and not os.path.isdir(ap) and BOS_SEP in ap:
|
2417
|
+
# strip leafs until it hits an existing folder
|
2418
|
+
ap = ap.rsplit(BOS_SEP, 1)[0]
|
2419
|
+
|
2413
2420
|
if ANYWIN:
|
2421
|
+
abspath = fsdec(ap)
|
2414
2422
|
bfree = ctypes.c_ulonglong(0)
|
2415
2423
|
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
|
2416
2424
|
ctypes.c_wchar_p(abspath), None, None, ctypes.pointer(bfree)
|
2417
2425
|
)
|
2418
|
-
return (bfree.value, None)
|
2426
|
+
return (bfree.value, None, "")
|
2419
2427
|
else:
|
2420
|
-
sv = os.statvfs(
|
2428
|
+
sv = os.statvfs(ap)
|
2421
2429
|
free = sv.f_frsize * sv.f_bfree
|
2422
2430
|
total = sv.f_frsize * sv.f_blocks
|
2423
|
-
return (free, total)
|
2424
|
-
except:
|
2425
|
-
return (None, None)
|
2431
|
+
return (free, total, "")
|
2432
|
+
except Exception as ex:
|
2433
|
+
return (None, None, repr(ex))
|
2426
2434
|
|
2427
2435
|
|
2428
2436
|
if not ANYWIN and not MACOS:
|
copyparty/web/a/u2c.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import print_function, unicode_literals
|
3
3
|
|
4
|
-
S_VERSION = "2.
|
5
|
-
S_BUILD_DT = "2024-10-
|
4
|
+
S_VERSION = "2.5"
|
5
|
+
S_BUILD_DT = "2024-10-18"
|
6
6
|
|
7
7
|
"""
|
8
8
|
u2c.py: upload to copyparty
|
@@ -154,6 +154,7 @@ class HCli(object):
|
|
154
154
|
self.tls = tls
|
155
155
|
self.verify = ar.te or not ar.td
|
156
156
|
self.conns = []
|
157
|
+
self.hconns = []
|
157
158
|
if tls:
|
158
159
|
import ssl
|
159
160
|
|
@@ -173,7 +174,7 @@ class HCli(object):
|
|
173
174
|
"User-Agent": "u2c/%s" % (S_VERSION,),
|
174
175
|
}
|
175
176
|
|
176
|
-
def _connect(self):
|
177
|
+
def _connect(self, timeout):
|
177
178
|
args = {}
|
178
179
|
if PY37:
|
179
180
|
args["blocksize"] = 1048576
|
@@ -185,7 +186,7 @@ class HCli(object):
|
|
185
186
|
if self.ctx:
|
186
187
|
args = {"context": self.ctx}
|
187
188
|
|
188
|
-
return C(self.addr, self.port, timeout=
|
189
|
+
return C(self.addr, self.port, timeout=timeout, **args)
|
189
190
|
|
190
191
|
def req(self, meth, vpath, hdrs, body=None, ctype=None):
|
191
192
|
hdrs.update(self.base_hdrs)
|
@@ -198,7 +199,9 @@ class HCli(object):
|
|
198
199
|
0 if not body else body.len if hasattr(body, "len") else len(body)
|
199
200
|
)
|
200
201
|
|
201
|
-
|
202
|
+
# large timeout for handshakes (safededup)
|
203
|
+
conns = self.hconns if ctype == MJ else self.conns
|
204
|
+
c = conns.pop() if conns else self._connect(999 if ctype == MJ else 128)
|
202
205
|
try:
|
203
206
|
c.request(meth, vpath, body, hdrs)
|
204
207
|
if PY27:
|
@@ -207,7 +210,7 @@ class HCli(object):
|
|
207
210
|
rsp = c.getresponse()
|
208
211
|
|
209
212
|
data = rsp.read()
|
210
|
-
|
213
|
+
conns.append(c)
|
211
214
|
return rsp.status, data.decode("utf-8")
|
212
215
|
except:
|
213
216
|
c.close()
|
@@ -868,9 +871,10 @@ def upload(fsl, stats, maxsz):
|
|
868
871
|
if sc >= 400:
|
869
872
|
raise Exception("http %s: %s" % (sc, txt))
|
870
873
|
finally:
|
871
|
-
fsl.f
|
872
|
-
|
873
|
-
|
874
|
+
if fsl.f:
|
875
|
+
fsl.f.close()
|
876
|
+
if nsub != -1:
|
877
|
+
fsl.unsub()
|
874
878
|
|
875
879
|
|
876
880
|
class Ctl(object):
|
copyparty/web/up2k.js.gz
CHANGED
Binary file
|
copyparty/web/w.hash.js.gz
CHANGED
Binary file
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.15.
|
3
|
+
Version: 1.15.9
|
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
|
@@ -101,6 +101,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
101
101
|
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
102
102
|
* [shares](#shares) - share a file or folder by creating a temporary link
|
103
103
|
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
104
|
+
* [rss feeds](#rss-feeds) - monitor a folder with your RSS reader
|
104
105
|
* [media player](#media-player) - plays almost every audio format there is
|
105
106
|
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
106
107
|
* [fix unreliable playback on android](#fix-unreliable-playback-on-android) - due to phone / app settings
|
@@ -899,6 +900,30 @@ or a mix of both:
|
|
899
900
|
the metadata keys you can use in the format field are the ones in the file-browser table header (whatever is collected with `-mte` and `-mtp`)
|
900
901
|
|
901
902
|
|
903
|
+
## rss feeds
|
904
|
+
|
905
|
+
monitor a folder with your RSS reader , optionally recursive
|
906
|
+
|
907
|
+
must be enabled per-volume with volflag `rss` or globally with `--rss`
|
908
|
+
|
909
|
+
the feed includes itunes metadata for use with podcast readers such as [AntennaPod](https://antennapod.org/)
|
910
|
+
|
911
|
+
a feed example: https://cd.ocv.me/a/d2/d22/?rss&fext=mp3
|
912
|
+
|
913
|
+
url parameters:
|
914
|
+
|
915
|
+
* `pw=hunter2` for password auth
|
916
|
+
* `recursive` to also include subfolders
|
917
|
+
* `title=foo` changes the feed title (default: folder name)
|
918
|
+
* `fext=mp3,opus` only include mp3 and opus files (default: all)
|
919
|
+
* `nf=30` only show the first 30 results (default: 250)
|
920
|
+
* `sort=m` sort by mtime (file last-modified), newest first (default)
|
921
|
+
* `u` = upload-time; NOTE: non-uploaded files have upload-time `0`
|
922
|
+
* `n` = filename
|
923
|
+
* `a` = filesize
|
924
|
+
* uppercase = reverse-sort; `M` = oldest file first
|
925
|
+
|
926
|
+
|
902
927
|
## media player
|
903
928
|
|
904
929
|
plays almost every audio format there is (if the server has FFmpeg installed for on-demand transcoding)
|
@@ -1,22 +1,22 @@
|
|
1
1
|
copyparty/__init__.py,sha256=Chqw7uXX4r_-a2p6-xthrrqVHFI4aZdW45sWU7UvqeE,2597
|
2
|
-
copyparty/__main__.py,sha256=
|
3
|
-
copyparty/__version__.py,sha256=
|
4
|
-
copyparty/authsrv.py,sha256
|
2
|
+
copyparty/__main__.py,sha256=rx8OlvcX3M-SQOvkrYT9C-HN7GmlPUx5p_2Vqv_LEo4,110949
|
3
|
+
copyparty/__version__.py,sha256=PMcIJPKN9PvxPb2mIFEiFAsp9HLavA8qaM4ckTc7cNE,258
|
4
|
+
copyparty/authsrv.py,sha256=Iw_4lJUhRU9q3qCQucGWRtJOFKYrYd5omL5nmwGvw-k,98979
|
5
5
|
copyparty/broker_mp.py,sha256=jsHUM2BSfRVRyZT869iPCqYEHSqedk6VkwvygZwbEZE,4017
|
6
6
|
copyparty/broker_mpw.py,sha256=PYFgQfssOCfdI6qayW1ZjO1j1-7oez094muhYMbPOz0,3339
|
7
7
|
copyparty/broker_thr.py,sha256=MXrwjusP0z1LPURUhi5jx_TL3jrXhYcDrJPDSKu6EEU,1705
|
8
8
|
copyparty/broker_util.py,sha256=76mfnFOpX1gUUvtjm8UQI7jpTIaVINX10QonM-B7ggc,1680
|
9
9
|
copyparty/cert.py,sha256=0ZAPeXeMR164vWn9GQU3JDKooYXEq_NOQkDeg543ivg,8009
|
10
|
-
copyparty/cfg.py,sha256=
|
10
|
+
copyparty/cfg.py,sha256=E9iBGNjIUrDAPLFRgKsVOmAknP9bDE27xh0gkmNdH1s,10127
|
11
11
|
copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
|
12
12
|
copyparty/fsutil.py,sha256=5CshJWO7CflfaRRNOb3JxghUH7W5rmS_HWNmKfx42MM,4538
|
13
13
|
copyparty/ftpd.py,sha256=G_h1urfIikzfCWGXnW9p-rioWdNM_Je6vWYq0-QSbC8,17580
|
14
|
-
copyparty/httpcli.py,sha256=
|
14
|
+
copyparty/httpcli.py,sha256=irIxsAI0KpNvxoenK16zkezttl4sUSNUB59yBr8L6VA,199053
|
15
15
|
copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
|
16
16
|
copyparty/httpsrv.py,sha256=d_UiGnQKniBoEV68lNFgnYm-byda7uj56mFf-YC7piI,17223
|
17
17
|
copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
|
18
18
|
copyparty/mdns.py,sha256=vC078llnL1v0pvL3mnwacuStFHPJUQuxo9Opj-IbHL4,18155
|
19
|
-
copyparty/metrics.py,sha256
|
19
|
+
copyparty/metrics.py,sha256=-1Rkk44gBh_1YJbdzGZHaqR4pEwkbno6fSdsRb5wDIk,8865
|
20
20
|
copyparty/mtag.py,sha256=8WGjEn0T0Ri9ww1yBpLUnFHZiTQMye1BMXL6SkK3MRo,18893
|
21
21
|
copyparty/multicast.py,sha256=Ha27l2oATEa-Qo2WOzkeRgjAm6G_YDCfbVJWR-ao2UE,12319
|
22
22
|
copyparty/pwhash.py,sha256=AdLMLyIi2IDhGtbKIQOswKUxWvO7ARYYRF_ThsryOoc,4124
|
@@ -30,9 +30,9 @@ copyparty/tcpsrv.py,sha256=l_vb9FoF0AJur0IoqHNUSBDqMgBO_MRUZeDszi1UNfY,19881
|
|
30
30
|
copyparty/tftpd.py,sha256=jZbf2JpeJmkuQWJErmAPG-dKhtYNvIUHbkAgodSXw9Y,13582
|
31
31
|
copyparty/th_cli.py,sha256=o6FMkerYvAXS455z3DUossVztu_nzFlYSQhs6qN6Jt8,4636
|
32
32
|
copyparty/th_srv.py,sha256=hI9wY1E_9N9Cgqvtr8zADeVqqiLGTiTdAnYAA7WFvJw,29346
|
33
|
-
copyparty/u2idx.py,sha256=
|
34
|
-
copyparty/up2k.py,sha256=
|
35
|
-
copyparty/util.py,sha256=
|
33
|
+
copyparty/u2idx.py,sha256=HLO49L1zmpJtBcJiXgD12a6pAlQdnf2pFelHMA7habw,13462
|
34
|
+
copyparty/up2k.py,sha256=2K21-Scz4c3_Y9MzAPmvq3_LEOIkFnx7KLR6hqJkP18,165419
|
35
|
+
copyparty/util.py,sha256=eJU7a5O-bZf_MEplCVmLoS3r5mjPC3-2khqZVTYGw-c,92653
|
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
|
@@ -80,12 +80,12 @@ copyparty/web/splash.js.gz,sha256=Xoccku-2vE3tABo-88q3Cl4koHs_AE76T8QvMy4u6T8,25
|
|
80
80
|
copyparty/web/svcs.html,sha256=P5YZimYLeQMT0uz6u3clQSNZRc5Zs0Ok-ffcbcGSYuc,11762
|
81
81
|
copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
|
82
82
|
copyparty/web/ui.css.gz,sha256=wloSacrHgP722hy4XiOvVY2GI9-V4zvfvzu84LLWS_o,2779
|
83
|
-
copyparty/web/up2k.js.gz,sha256=
|
83
|
+
copyparty/web/up2k.js.gz,sha256=lGR1Xb0RkIZ1eHmncsSwWRuFc6FC2rZalvjo3oNTV1s,23291
|
84
84
|
copyparty/web/util.js.gz,sha256=NvjPYhIa0-C_NhUyW-Ra-XinUCRjj8G3pYq1zJHYWEk,14805
|
85
|
-
copyparty/web/w.hash.js.gz,sha256=
|
85
|
+
copyparty/web/w.hash.js.gz,sha256=l3GpSJD6mcU-1CRWkIj7PybgbjlfSr8oeO3vortIrQk,1105
|
86
86
|
copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
87
87
|
copyparty/web/a/partyfuse.py,sha256=fa9bBYNJHvtWpNVjQyyFzx6JOK7MJL1u0zj80PBYQKs,27960
|
88
|
-
copyparty/web/a/u2c.py,sha256=
|
88
|
+
copyparty/web/a/u2c.py,sha256=ZmLcGuOWB66ZkAMpJBiR1Xpa75PgpzdFRTt3GGVCorc,49533
|
89
89
|
copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
|
90
90
|
copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
|
91
91
|
copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
|
@@ -106,9 +106,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
|
|
106
106
|
copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
|
107
107
|
copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
|
108
108
|
copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
|
109
|
-
copyparty-1.15.
|
110
|
-
copyparty-1.15.
|
111
|
-
copyparty-1.15.
|
112
|
-
copyparty-1.15.
|
113
|
-
copyparty-1.15.
|
114
|
-
copyparty-1.15.
|
109
|
+
copyparty-1.15.9.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
|
110
|
+
copyparty-1.15.9.dist-info/METADATA,sha256=1GMM2x8ZO0z3TgiQgCB6xEiiahXsn3PdeiA7Uni_D_4,139807
|
111
|
+
copyparty-1.15.9.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
112
|
+
copyparty-1.15.9.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
|
113
|
+
copyparty-1.15.9.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
|
114
|
+
copyparty-1.15.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|