Compare commits

...

23 Commits

Author SHA1 Message Date
ed
02c7061945 v0.12.8 2021-08-01 00:17:05 +02:00
ed
9209e44cd3 heh 2021-08-01 00:08:50 +02:00
ed
ebed37394e better rename ui 2021-08-01 00:04:53 +02:00
ed
4c7a2a7ec3 uridec alerts 2021-07-31 22:05:31 +02:00
ed
0a25a88a34 add mojibake fixer 2021-07-31 14:31:39 +02:00
ed
6aa9025347 v0.12.7 2021-07-31 13:21:43 +02:00
ed
a918cc67eb only drop tags when its safe 2021-07-31 13:19:02 +02:00
ed
08f4695283 v0.12.6 2021-07-31 12:38:53 +02:00
ed
44e76d5eeb optimize make-sfx 2021-07-31 12:38:17 +02:00
ed
cfa36fd279 phone-friendly toast positioning 2021-07-31 10:56:03 +02:00
ed
3d4166e006 dont thumbnail thumbnails 2021-07-31 10:51:18 +02:00
ed
07bac1c592 add option to show dotfiles 2021-07-31 10:44:35 +02:00
ed
755f2ce1ba more url encoding fun 2021-07-31 10:24:34 +02:00
ed
cca2844deb fix mode display for move 2021-07-31 07:19:10 +00:00
ed
24a2f760b7 v0.12.5 2021-07-30 19:28:14 +02:00
ed
79bbd8fe38 systemd: line-buffered logging 2021-07-30 10:39:46 +02:00
ed
35dce1e3e4 v0.12.4 2021-07-30 08:52:15 +02:00
ed
f886fdf913 mention unpost in the readme 2021-07-30 00:53:15 +02:00
ed
4476f2f0da v0.12.3 orz 2021-07-30 00:32:21 +02:00
ed
160f161700 v0.12.2 (1000GET) 2021-07-29 23:56:25 +02:00
ed
c164fc58a2 add unpost 2021-07-29 23:53:08 +02:00
ed
0c625a4e62 store upload ip and time 2021-07-29 00:30:10 +02:00
ed
bf3941cf7a v0.12.1 2021-07-28 01:55:01 +02:00
22 changed files with 677 additions and 189 deletions

View File

@@ -127,6 +127,7 @@ summary: all planned features work! now please enjoy the bloatening
* ☑ basic: plain multipart, ie6 support * ☑ basic: plain multipart, ie6 support
* ☑ up2k: js, resumable, multithreaded * ☑ up2k: js, resumable, multithreaded
* ☑ stash: simple PUT filedropper * ☑ stash: simple PUT filedropper
* ☑ unpost: undo/delete accidental uploads
* ☑ symlink/discard existing files (content-matching) * ☑ symlink/discard existing files (content-matching)
* download * download
* ☑ single files in browser * ☑ single files in browser
@@ -215,10 +216,11 @@ example:
## tabs ## tabs
* `[🔎]` search by size, date, path/name, mp3-tags ... see [searching](#searching) * `[🔎]` search by size, date, path/name, mp3-tags ... see [searching](#searching)
* `[🧯]` unpost: undo/delete accidental uploads
* `[🚀]` and `[🎈]` are the uploaders, see [uploading](#uploading) * `[🚀]` and `[🎈]` are the uploaders, see [uploading](#uploading)
* `[📂]` mkdir, create directories * `[📂]` mkdir: create directories
* `[📝]` new-md, create a new markdown document * `[📝]` new-md: create a new markdown document
* `[📟]` send-msg, either to server-log or into textfiles if `--urlform save` * `[📟]` send-msg: either to server-log or into textfiles if `--urlform save`
* `[🎺]` audio-player config options * `[🎺]` audio-player config options
* `[⚙️]` general client config options * `[⚙️]` general client config options
@@ -312,8 +314,10 @@ you can also zip a selection of files or folders by clicking them in the browser
## uploading ## uploading
two upload methods are available in the html client: two upload methods are available in the html client:
* `🎈 bup`, the basic uploader, supports almost every browser since netscape 4.0 * `[🎈] bup`, the basic uploader, supports almost every browser since netscape 4.0
* `🚀 up2k`, the fancy one * `[🚀] up2k`, the fancy one
you can undo/delete uploads using `[🧯] unpost` if the server is running with `-e2d`
up2k has several advantages: up2k has several advantages:
* you can drop folders into the browser (files are added recursively) * you can drop folders into the browser (files are added recursively)

View File

@@ -13,6 +13,10 @@
# But note that journalctl will get the timestamps wrong due to # But note that journalctl will get the timestamps wrong due to
# python disabling line-buffering, so messages are out-of-order: # python disabling line-buffering, so messages are out-of-order:
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png # https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
#
# enable line-buffering for realtime logging (slight performance cost):
# modify ExecStart and prefix it with `/bin/stdbuf -oL` like so:
# ExecStart=/bin/stdbuf -oL /usr/bin/python3 [...]
[Unit] [Unit]
Description=copyparty file server Description=copyparty file server

View File

@@ -264,9 +264,12 @@ def run_argparse(argv, formatter):
ap2.add_argument("-ed", action="store_true", help="enable ?dots") ap2.add_argument("-ed", action="store_true", help="enable ?dots")
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins") ap2.add_argument("-emp", action="store_true", help="enable markdown plugins")
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate") ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
ap2 = ap.add_argument_group('upload options')
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads") ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)") ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]") ap2.add_argument("--unpost", metavar="SEC", type=int, default=3600*12, help="grace period where uploads can be deleted by the uploader, even without delete permissions; 0=disabled")
ap2 = ap.add_argument_group('network options') ap2 = ap.add_argument_group('network options')
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)") ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
@@ -319,25 +322,27 @@ def run_argparse(argv, formatter):
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age") ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for") ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
ap2 = ap.add_argument_group('database options') ap2 = ap.add_argument_group('general db options')
ap2.add_argument("-e2d", action="store_true", help="enable up2k database") ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d") ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds") ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
ap2 = ap.add_argument_group('metadata db options')
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing") ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t") ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts") ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead") ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead")
ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism") ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism")
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader") ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping") ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)", ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps") default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin") ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
ap2 = ap.add_argument_group('appearance options') ap2 = ap.add_argument_group('appearance options')
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include") ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
@@ -397,7 +402,7 @@ def main(argv=None):
for opt in oa[2:]: for opt in oa[2:]:
if re.match("c[^,]", opt): if re.match("c[^,]", opt):
mod = True mod = True
na.append("c," + opt[2:]) na.append("c," + opt[1:])
elif re.sub("^[rwmd]*", "", opt) and "," not in opt: elif re.sub("^[rwmd]*", "", opt) and "," not in opt:
mod = True mod = True
perm = opt[0] perm = opt[0]

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 12, 0) VERSION = (0, 12, 8)
CODENAME = "fil\033[33med" CODENAME = "fil\033[33med"
BUILD_DT = (2021, 7, 28) BUILD_DT = (2021, 8, 1)
S_VERSION = ".".join(map(str, VERSION)) S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT) S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -401,17 +401,18 @@ class AuthSrv(object):
if uname == "": if uname == "":
uname = "*" uname = "*"
if "r" in lvl: for un in uname.split(","):
axs.uread[uname] = 1 if "r" in lvl:
axs.uread[un] = 1
if "w" in lvl: if "w" in lvl:
axs.uwrite[uname] = 1 axs.uwrite[un] = 1
if "m" in lvl: if "m" in lvl:
axs.umove[uname] = 1 axs.umove[un] = 1
if "d" in lvl: if "d" in lvl:
axs.udel[uname] = 1 axs.udel[un] = 1
def _read_volflag(self, flags, name, value, is_list): def _read_volflag(self, flags, name, value, is_list):
if name not in ["mtp"]: if name not in ["mtp"]:
@@ -795,7 +796,7 @@ class AuthSrv(object):
atop = vn.realpath atop = vn.realpath
g = vn.walk( g = vn.walk(
"", "", [], u, True, [[True]], not self.args.no_scandir, False vn.vpath, "", [], u, [[True]], True, not self.args.no_scandir, False
) )
for _, _, vpath, apath, files, _, _ in g: for _, _, vpath, apath, files, _, _ in g:
fnames = [n[0] for n in files] fnames = [n[0] for n in files]

View File

@@ -61,7 +61,10 @@ class HttpCli(object):
a, b = m.groups() a, b = m.groups()
return "=\033[7m {} \033[27m{}".format(self.asrv.iacct[a], b) return "=\033[7m {} \033[27m{}".format(self.asrv.iacct[a], b)
def _check_nonfatal(self, ex): def _check_nonfatal(self, ex, post):
if post:
return ex.code < 300
return ex.code < 400 or ex.code in [404, 429] return ex.code < 400 or ex.code in [404, 429]
def _assert_safe_rem(self, rem): def _assert_safe_rem(self, rem):
@@ -103,7 +106,7 @@ class HttpCli(object):
self.req = "[junk]" self.req = "[junk]"
self.http_ver = "HTTP/1.1" self.http_ver = "HTTP/1.1"
# self.log("pebkac at httpcli.run #1: " + repr(ex)) # self.log("pebkac at httpcli.run #1: " + repr(ex))
self.keepalive = self._check_nonfatal(ex) self.keepalive = False
self.loud_reply(unicode(ex), status=ex.code) self.loud_reply(unicode(ex), status=ex.code)
return self.keepalive return self.keepalive
@@ -179,7 +182,7 @@ class HttpCli(object):
self.uparam = uparam self.uparam = uparam
self.cookies = cookies self.cookies = cookies
self.vpath = unquotep(vpath) self.vpath = unquotep(vpath) # not query, so + means +
pwd = uparam.get("pw") pwd = uparam.get("pw")
self.uname = self.asrv.iacct.get(pwd, "*") self.uname = self.asrv.iacct.get(pwd, "*")
@@ -216,7 +219,8 @@ class HttpCli(object):
except Pebkac as ex: except Pebkac as ex:
try: try:
# self.log("pebkac at httpcli.run #2: " + repr(ex)) # self.log("pebkac at httpcli.run #2: " + repr(ex))
if not self._check_nonfatal(ex): post = self.mode in ["POST", "PUT"] or "content-length" in self.headers
if not self._check_nonfatal(ex, post):
self.keepalive = False self.keepalive = False
self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3) self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
@@ -342,11 +346,36 @@ class HttpCli(object):
static_path = os.path.join(E.mod, "web/", self.vpath[5:]) static_path = os.path.join(E.mod, "web/", self.vpath[5:])
return self.tx_file(static_path) return self.tx_file(static_path)
x = self.asrv.vfs.can_access(self.vpath, self.uname)
self.can_read, self.can_write, self.can_move, self.can_delete = x
if not self.can_read and not self.can_write:
if self.vpath:
self.log("inaccessible: [{}]".format(self.vpath))
raise Pebkac(404)
self.uparam["h"] = False
if "tree" in self.uparam: if "tree" in self.uparam:
return self.tx_tree() return self.tx_tree()
if "stack" in self.uparam: if "delete" in self.uparam:
return self.tx_stack() return self.handle_rm()
if "move" in self.uparam:
return self.handle_mv()
if "scan" in self.uparam:
return self.scanvol()
if not self.vpath:
if "stack" in self.uparam:
return self.tx_stack()
if "ups" in self.uparam:
return self.tx_ups()
if "h" in self.uparam:
return self.tx_mounts()
# conditional redirect to single volumes # conditional redirect to single volumes
if self.vpath == "" and not self.ouparam: if self.vpath == "" and not self.ouparam:
@@ -362,28 +391,6 @@ class HttpCli(object):
self.redirect(vpath, flavor="redirecting to", use302=True) self.redirect(vpath, flavor="redirecting to", use302=True)
return True return True
x = self.asrv.vfs.can_access(self.vpath, self.uname)
self.can_read, self.can_write, self.can_move, self.can_delete = x
if not self.can_read and not self.can_write:
if self.vpath:
self.log("inaccessible: [{}]".format(self.vpath))
raise Pebkac(404)
self.uparam = {"h": False}
if "delete" in self.uparam:
return self.handle_rm()
if "move" in self.uparam:
return self.handle_mv()
if "h" in self.uparam:
self.vpath = None
return self.tx_mounts()
if "scan" in self.uparam:
return self.scanvol()
return self.tx_browser() return self.tx_browser()
def handle_options(self): def handle_options(self):
@@ -498,7 +505,14 @@ class HttpCli(object):
if not self.args.nw: if not self.args.nw:
vfs, vrem = vfs.get_dbv(rem) vfs, vrem = vfs.get_dbv(rem)
self.conn.hsrv.broker.put( self.conn.hsrv.broker.put(
False, "up2k.hash_file", vfs.realpath, vfs.flags, vrem, fn False,
"up2k.hash_file",
vfs.realpath,
vfs.flags,
vrem,
fn,
self.ip,
time.time(),
) )
return post_sz, sha_b64, remains, path return post_sz, sha_b64, remains, path
@@ -592,6 +606,9 @@ class HttpCli(object):
if "srch" in self.uparam or "srch" in body: if "srch" in self.uparam or "srch" in body:
return self.handle_search(body) return self.handle_search(body)
if "delete" in self.uparam:
return self.handle_rm(body)
# up2k-php compat # up2k-php compat
for k in "chunkpit.php", "handshake.php": for k in "chunkpit.php", "handshake.php":
if self.vpath.endswith(k): if self.vpath.endswith(k):
@@ -905,6 +922,8 @@ class HttpCli(object):
dbv.flags, dbv.flags,
vrem, vrem,
fname, fname,
self.ip,
time.time(),
) )
self.conn.nbyte += sz self.conn.nbyte += sz
@@ -1291,11 +1310,9 @@ class HttpCli(object):
else: else:
fn = self.headers.get("host", "hey") fn = self.headers.get("host", "hey")
afn = "".join( safe = (string.ascii_letters + string.digits).replace("%", "")
[x if x in (string.ascii_letters + string.digits) else "_" for x in fn] afn = "".join([x if x in safe.replace('"', "") else "_" for x in fn])
) bascii = unicode(safe).encode("utf-8")
bascii = unicode(string.ascii_letters + string.digits).encode("utf-8")
ufn = fn.encode("utf-8", "xmlcharrefreplace") ufn = fn.encode("utf-8", "xmlcharrefreplace")
if PY2: if PY2:
ufn = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in ufn] ufn = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in ufn]
@@ -1310,6 +1327,7 @@ class HttpCli(object):
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}" cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
cdis = cdis.format(afn, fmt, ufn, fmt) cdis = cdis.format(afn, fmt, ufn, fmt)
self.log(cdis)
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis}) self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir) fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir)
@@ -1542,14 +1560,52 @@ class HttpCli(object):
ret["a"] = dirs ret["a"] = dirs
return ret return ret
def handle_rm(self): def tx_ups(self):
if not self.can_delete: if not self.args.unpost:
raise Pebkac(400, "the unpost feature was disabled by server config")
filt = self.uparam.get("filter")
lm = "ups [{}]".format(filt)
self.log(lm)
ret = []
t0 = time.time()
idx = self.conn.get_u2idx()
lim = time.time() - self.args.unpost
for vol in self.asrv.vfs.all_vols.values():
cur = idx.get_cur(vol.realpath)
if not cur:
continue
q = "select sz, rd, fn, at from up where ip=? and at>?"
for sz, rd, fn, at in cur.execute(q, (self.ip, lim)):
vp = "/" + "/".join([rd, fn]).strip("/")
if filt and filt not in vp:
continue
ret.append({"vp": vp, "sz": sz, "at": at})
if len(ret) > 3000:
ret.sort(key=lambda x: x["at"], reverse=True)
ret = ret[:2000]
ret.sort(key=lambda x: x["at"], reverse=True)
ret = ret[:2000]
jtxt = json.dumps(ret, indent=2, sort_keys=True).encode("utf-8", "replace")
self.log("{} #{} {:.2f}sec".format(lm, len(ret), time.time() - t0))
self.reply(jtxt, mime="application/json")
def handle_rm(self, req=None):
if not req and not self.can_delete:
raise Pebkac(403, "not allowed for user " + self.uname) raise Pebkac(403, "not allowed for user " + self.uname)
if self.args.no_del: if self.args.no_del:
raise Pebkac(403, "disabled by argv") raise Pebkac(403, "disabled by argv")
x = self.conn.hsrv.broker.put(True, "up2k.handle_rm", self.uname, self.vpath) if not req:
req = [self.vpath]
x = self.conn.hsrv.broker.put(True, "up2k.handle_rm", self.uname, self.ip, req)
self.loud_reply(x.get()) self.loud_reply(x.get())
def handle_mv(self): def handle_mv(self):
@@ -1564,6 +1620,9 @@ class HttpCli(object):
if not dst: if not dst:
raise Pebkac(400, "need dst vpath") raise Pebkac(400, "need dst vpath")
# x-www-form-urlencoded (url query part) uses
# either + or %20 for 0x20 so handle both
dst = unquotep(dst.replace("+", " "))
x = self.conn.hsrv.broker.put( x = self.conn.hsrv.broker.put(
True, "up2k.handle_mv", self.uname, self.vpath, dst True, "up2k.handle_mv", self.uname, self.vpath, dst
) )
@@ -1702,6 +1761,7 @@ class HttpCli(object):
"have_mv": (not self.args.no_mv), "have_mv": (not self.args.no_mv),
"have_del": (not self.args.no_del), "have_del": (not self.args.no_del),
"have_zip": (not self.args.no_zip), "have_zip": (not self.args.no_zip),
"have_unpost": (self.args.unpost > 0),
"have_b_u": (self.can_write and self.uparam.get("b") == "u"), "have_b_u": (self.can_write and self.uparam.get("b") == "u"),
"url_suf": url_suf, "url_suf": url_suf,
"logues": logues, "logues": logues,

View File

@@ -26,6 +26,9 @@ class ThumbCli(object):
if is_vid and self.args.no_vthumb: if is_vid and self.args.no_vthumb:
return None return None
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
return os.path.join(ptop, rem)
if fmt == "j" and self.args.th_no_jpg: if fmt == "j" and self.args.th_no_jpg:
fmt = "w" fmt = "w"

View File

@@ -244,7 +244,7 @@ class U2idx(object):
sret = [] sret = []
c = cur.execute(q, v) c = cur.execute(q, v)
for hit in c: for hit in c:
w, ts, sz, rd, fn = hit w, ts, sz, rd, fn, ip, at = hit
lim -= 1 lim -= 1
if lim <= 0: if lim <= 0:
break break

View File

@@ -45,7 +45,7 @@ try:
except: except:
HAVE_SQLITE3 = False HAVE_SQLITE3 = False
DB_VER = 4 DB_VER = 5
class Up2k(object): class Up2k(object):
@@ -522,7 +522,7 @@ class Up2k(object):
wark = up2k_wark_from_hashlist(self.salt, sz, hashes) wark = up2k_wark_from_hashlist(self.salt, sz, hashes)
self.db_add(dbw[0], wark, rd, fn, lmod, sz) self.db_add(dbw[0], wark, rd, fn, lmod, sz, "", 0)
dbw[1] += 1 dbw[1] += 1
ret += 1 ret += 1
td = time.time() - dbw[2] td = time.time() - dbw[2]
@@ -537,8 +537,8 @@ class Up2k(object):
rm = [] rm = []
nchecked = 0 nchecked = 0
nfiles = next(cur.execute("select count(w) from up"))[0] nfiles = next(cur.execute("select count(w) from up"))[0]
c = cur.execute("select * from up") c = cur.execute("select rd, fn from up")
for dwark, dts, dsz, drd, dfn in c: for drd, dfn in c:
nchecked += 1 nchecked += 1
if drd.startswith("//") or dfn.startswith("//"): if drd.startswith("//") or dfn.startswith("//"):
drd, dfn = s3dec(drd, dfn) drd, dfn = s3dec(drd, dfn)
@@ -941,6 +941,15 @@ class Up2k(object):
if not existed and ver is None: if not existed and ver is None:
return self._create_db(db_path, cur) return self._create_db(db_path, cur)
if ver == 4:
try:
m = "creating backup before upgrade: "
cur = self._backup_db(db_path, cur, ver, m)
self._upgrade_v4(cur)
ver = 5
except:
self.log("WARN: failed to upgrade from v4", 3)
if ver == DB_VER: if ver == DB_VER:
try: try:
nfiles = next(cur.execute("select count(w) from up"))[0] nfiles = next(cur.execute("select count(w) from up"))[0]
@@ -1011,9 +1020,10 @@ class Up2k(object):
idx = r"create index up_w on up(w)" idx = r"create index up_w on up(w)"
for cmd in [ for cmd in [
r"create table up (w text, mt int, sz int, rd text, fn text)", r"create table up (w text, mt int, sz int, rd text, fn text, ip text, at int)",
r"create index up_rd on up(rd)", r"create index up_rd on up(rd)",
r"create index up_fn on up(fn)", r"create index up_fn on up(fn)",
r"create index up_ip on up(ip)",
idx, idx,
r"create table mt (w text, k text, v int)", r"create table mt (w text, k text, v int)",
r"create index mt_w on mt(w)", r"create index mt_w on mt(w)",
@@ -1028,6 +1038,17 @@ class Up2k(object):
self.log("created DB at {}".format(db_path)) self.log("created DB at {}".format(db_path))
return cur return cur
def _upgrade_v4(self, cur):
for cmd in [
r"alter table up add column ip text",
r"alter table up add column at int",
r"create index up_ip on up(ip)",
r"update kv set v=5 where k='sver'",
]:
cur.execute(cmd)
cur.connection.commit()
def handle_json(self, cj): def handle_json(self, cj):
with self.mutex: with self.mutex:
if not self.register_vpath(cj["ptop"], cj["vcfg"]): if not self.register_vpath(cj["ptop"], cj["vcfg"]):
@@ -1051,7 +1072,7 @@ class Up2k(object):
argv = (wark[:16], wark) argv = (wark[:16], wark)
cur = cur.execute(q, argv) cur = cur.execute(q, argv)
for _, dtime, dsize, dp_dir, dp_fn in cur: for _, dtime, dsize, dp_dir, dp_fn, ip, at in cur:
if dp_dir.startswith("//") or dp_fn.startswith("//"): if dp_dir.startswith("//") or dp_fn.startswith("//"):
dp_dir, dp_fn = s3dec(dp_dir, dp_fn) dp_dir, dp_fn = s3dec(dp_dir, dp_fn)
@@ -1065,6 +1086,8 @@ class Up2k(object):
"ptop": cj["ptop"], "ptop": cj["ptop"],
"size": dsize, "size": dsize,
"lmod": dtime, "lmod": dtime,
"addr": ip,
"at": at,
"hash": [], "hash": [],
"need": [], "need": [],
} }
@@ -1119,7 +1142,8 @@ class Up2k(object):
self._symlink(src, dst) self._symlink(src, dst)
if cur: if cur:
a = [cj[x] for x in "prel name lmod size".split()] a = [cj[x] for x in "prel name lmod size addr".split()]
a += [cj.get("at") or time.time()]
self.db_add(cur, wark, *a) self.db_add(cur, wark, *a)
cur.connection.commit() cur.connection.commit()
@@ -1266,20 +1290,21 @@ class Up2k(object):
a = [dst, job["size"], (int(time.time()), int(job["lmod"]))] a = [dst, job["size"], (int(time.time()), int(job["lmod"]))]
self.lastmod_q.put(a) self.lastmod_q.put(a)
a = [job[x] for x in "ptop wark prel name lmod size".split()] a = [job[x] for x in "ptop wark prel name lmod size addr".split()]
a += [job.get("at") or time.time()]
if self.idx_wark(*a): if self.idx_wark(*a):
del self.registry[ptop][wark] del self.registry[ptop][wark]
# in-memory registry is reserved for unfinished uploads # in-memory registry is reserved for unfinished uploads
return ret, dst return ret, dst
def idx_wark(self, ptop, wark, rd, fn, lmod, sz): def idx_wark(self, ptop, wark, rd, fn, lmod, sz, ip, at):
cur = self.cur.get(ptop) cur = self.cur.get(ptop)
if not cur: if not cur:
return False return False
self.db_rm(cur, rd, fn) self.db_rm(cur, rd, fn)
self.db_add(cur, wark, rd, fn, lmod, sz) self.db_add(cur, wark, rd, fn, lmod, sz, ip, at)
cur.connection.commit() cur.connection.commit()
if "e2t" in self.flags[ptop]: if "e2t" in self.flags[ptop]:
@@ -1295,53 +1320,99 @@ class Up2k(object):
except: except:
db.execute(sql, s3enc(self.mem_cur, rd, fn)) db.execute(sql, s3enc(self.mem_cur, rd, fn))
def db_add(self, db, wark, rd, fn, ts, sz): def db_add(self, db, wark, rd, fn, ts, sz, ip, at):
sql = "insert into up values (?,?,?,?,?)" sql = "insert into up values (?,?,?,?,?,?,?)"
v = (wark, int(ts), sz, rd, fn) v = (wark, int(ts), sz, rd, fn, ip or "", int(at or 0))
try: try:
db.execute(sql, v) db.execute(sql, v)
except: except:
rd, fn = s3enc(self.mem_cur, rd, fn) rd, fn = s3enc(self.mem_cur, rd, fn)
v = (wark, int(ts), sz, rd, fn) v = (wark, int(ts), sz, rd, fn, ip or "", int(at or 0))
db.execute(sql, v) db.execute(sql, v)
def handle_rm(self, uname, vpath): def handle_rm(self, uname, ip, vpaths):
permsets = [[True, False, False, True]] n_files = 0
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0]) ok = {}
ng = {}
for vp in vpaths:
a, b, c = self._handle_rm(uname, ip, vp)
n_files += a
for k in b:
ok[k] = 1
for k in c:
ng[k] = 1
ng = {k: 1 for k in ng if k not in ok}
ok = len(ok)
ng = len(ng)
return "deleted {} files (and {}/{} folders)".format(n_files, ok, ok + ng)
def _handle_rm(self, uname, ip, vpath):
try:
permsets = [[True, False, False, True]]
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
unpost = False
except:
# unpost with missing permissions? try read+write and verify with db
if not self.args.unpost:
raise Pebkac(400, "the unpost feature was disabled by server config")
unpost = True
permsets = [[True, True]]
vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
_, _, _, _, dip, dat = self._find_from_vpath(vn.realpath, rem)
m = "you cannot delete this: "
if not dip:
m += "file not found"
elif dip != ip:
m += "not uploaded by (You)"
elif dat < time.time() - self.args.unpost:
m += "uploaded too long ago"
else:
m = None
if m:
raise Pebkac(400, m)
ptop = vn.realpath ptop = vn.realpath
atop = vn.canonical(rem) atop = vn.canonical(rem, False)
adir, fn = os.path.split(atop) adir, fn = os.path.split(atop)
st = bos.lstat(atop) st = bos.lstat(atop)
scandir = not self.args.no_scandir scandir = not self.args.no_scandir
if stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode): if stat.S_ISLNK(st.st_mode) or stat.S_ISREG(st.st_mode):
dbv, vrem = self.asrv.vfs.get(vpath, uname, *permsets[0]) dbv, vrem = self.asrv.vfs.get(vpath, uname, *permsets[0])
dbv, vrem = dbv.get_dbv(vrem) dbv, vrem = dbv.get_dbv(vrem)
g = [[dbv, vrem, os.path.dirname(vpath), adir, [[fn, 0]], [], []]] voldir = vsplit(vrem)[0]
vpath_dir = vsplit(vpath)[0]
g = [[dbv, voldir, vpath_dir, adir, [[fn, 0]], [], []]]
else: else:
g = vn.walk("", rem, [], uname, permsets, True, scandir, True) g = vn.walk("", rem, [], uname, permsets, True, scandir, True)
if unpost:
raise Pebkac(400, "cannot unpost folders")
n_files = 0 n_files = 0
for dbv, vrem, _, adir, files, rd, vd in g: for dbv, vrem, _, adir, files, rd, vd in g:
for fn in [x[0] for x in files]: for fn in [x[0] for x in files]:
n_files += 1 n_files += 1
abspath = os.path.join(adir, fn) abspath = os.path.join(adir, fn)
vpath = "{}/{}".format(vrem, fn).strip("/") volpath = "{}/{}".format(vrem, fn).strip("/")
vpath = "{}/{}".format(dbv.vpath, volpath).strip("/")
self.log("rm {}\n {}".format(vpath, abspath)) self.log("rm {}\n {}".format(vpath, abspath))
_ = dbv.get(vrem, uname, *permsets[0]) _ = dbv.get(volpath, uname, *permsets[0])
with self.mutex: with self.mutex:
try: try:
ptop = dbv.realpath ptop = dbv.realpath
cur, wark, _, _ = self._find_from_vpath(ptop, vrem) cur, wark, _, _, _, _ = self._find_from_vpath(ptop, volpath)
self._forget_file(ptop, vpath, cur, wark) self._forget_file(ptop, volpath, cur, wark, True)
finally: finally:
cur.connection.commit() cur.connection.commit()
bos.unlink(abspath) bos.unlink(abspath)
rm = rmdirs(self.log_func, scandir, True, atop) rm = rmdirs(self.log_func, scandir, True, atop)
ok = len(rm[0]) return n_files, rm[0], rm[1]
ng = len(rm[1])
return "deleted {} files (and {}/{} folders)".format(n_files, ok, ok + ng)
def handle_mv(self, uname, svp, dvp): def handle_mv(self, uname, svp, dvp):
svn, srem = self.asrv.vfs.get(svp, uname, True, False, True) svn, srem = self.asrv.vfs.get(svp, uname, True, False, True)
@@ -1411,7 +1482,7 @@ class Up2k(object):
self.need_rescan[dvn.vpath] = 1 self.need_rescan[dvn.vpath] = 1
return "k" return "k"
c1, w, ftime, fsize = self._find_from_vpath(svn.realpath, srem) c1, w, ftime, fsize, ip, at = self._find_from_vpath(svn.realpath, srem)
c2 = self.cur.get(dvn.realpath) c2 = self.cur.get(dvn.realpath)
if ftime is None: if ftime is None:
@@ -1420,15 +1491,15 @@ class Up2k(object):
fsize = st.st_size fsize = st.st_size
if w: if w:
if c2: if c2 and c2 != c1:
self._copy_tags(c1, c2, w) self._copy_tags(c1, c2, w)
self._forget_file(svn.realpath, srem, c1, w) self._forget_file(svn.realpath, srem, c1, w, c1 != c2)
self._relink(w, svn.realpath, srem, dabs) self._relink(w, svn.realpath, srem, dabs)
c1.connection.commit() c1.connection.commit()
if c2: if c2:
self.db_add(c2, w, drd, dfn, ftime, fsize) self.db_add(c2, w, drd, dfn, ftime, fsize, ip, at)
c2.connection.commit() c2.connection.commit()
else: else:
self.log("not found in src db: [{}]".format(svp)) self.log("not found in src db: [{}]".format(svp))
@@ -1452,7 +1523,7 @@ class Up2k(object):
return None, None return None, None
rd, fn = vsplit(vrem) rd, fn = vsplit(vrem)
q = "select w, mt, sz from up where rd=? and fn=? limit 1" q = "select w, mt, sz, ip, at from up where rd=? and fn=? limit 1"
try: try:
c = cur.execute(q, (rd, fn)) c = cur.execute(q, (rd, fn))
except: except:
@@ -1460,21 +1531,23 @@ class Up2k(object):
hit = c.fetchone() hit = c.fetchone()
if hit: if hit:
wark, ftime, fsize = hit wark, ftime, fsize, ip, at = hit
return cur, wark, ftime, fsize return cur, wark, ftime, fsize, ip, at
return cur, None, None, None return cur, None, None, None, None, None
def _forget_file(self, ptop, vrem, cur, wark): def _forget_file(self, ptop, vrem, cur, wark, drop_tags):
"""forgets file in db, fixes symlinks, does not delete""" """forgets file in db, fixes symlinks, does not delete"""
srd, sfn = vsplit(vrem) srd, sfn = vsplit(vrem)
self.log("forgetting {}".format(vrem)) self.log("forgetting {}".format(vrem))
if wark: if wark:
self.log("found {} in db".format(wark)) self.log("found {} in db".format(wark))
self._relink(wark, ptop, vrem, None) if self._relink(wark, ptop, vrem, None):
drop_tags = False
q = "delete from mt where w=?" if drop_tags:
cur.execute(q, (wark[:16],)) q = "delete from mt where w=?"
self.db_rm(cur, srd, sfn) cur.execute(q, (wark[:16],))
self.db_rm(cur, srd, sfn)
reg = self.registry.get(ptop) reg = self.registry.get(ptop)
if reg: if reg:
@@ -1510,7 +1583,7 @@ class Up2k(object):
self.log("found {} dupe: [{}] {}".format(wark, ptop, dvrem)) self.log("found {} dupe: [{}] {}".format(wark, ptop, dvrem))
if not dupes: if not dupes:
return return 0
full = {} full = {}
links = {} links = {}
@@ -1547,6 +1620,8 @@ class Up2k(object):
self._symlink(dabs, alink, False) self._symlink(dabs, alink, False)
return len(full) + len(links)
def _get_wark(self, cj): def _get_wark(self, cj):
if len(cj["name"]) > 1024 or len(cj["hash"]) > 512 * 1024: # 16TiB if len(cj["name"]) > 1024 or len(cj["hash"]) > 512 * 1024: # 16TiB
raise Pebkac(400, "name or numchunks not according to spec") raise Pebkac(400, "name or numchunks not according to spec")
@@ -1753,7 +1828,7 @@ class Up2k(object):
self.n_hashq -= 1 self.n_hashq -= 1
# self.log("hashq {}".format(self.n_hashq)) # self.log("hashq {}".format(self.n_hashq))
ptop, rd, fn = self.hashq.get() ptop, rd, fn, ip, at = self.hashq.get()
# self.log("hashq {} pop {}/{}/{}".format(self.n_hashq, ptop, rd, fn)) # self.log("hashq {} pop {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
if "e2d" not in self.flags[ptop]: if "e2d" not in self.flags[ptop]:
continue continue
@@ -1764,12 +1839,12 @@ class Up2k(object):
hashes = self._hashlist_from_file(abspath) hashes = self._hashlist_from_file(abspath)
wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes) wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes)
with self.mutex: with self.mutex:
self.idx_wark(ptop, wark, rd, fn, inf.st_mtime, inf.st_size) self.idx_wark(ptop, wark, rd, fn, inf.st_mtime, inf.st_size, ip, at)
def hash_file(self, ptop, flags, rd, fn): def hash_file(self, ptop, flags, rd, fn, ip, at):
with self.mutex: with self.mutex:
self.register_vpath(ptop, flags) self.register_vpath(ptop, flags)
self.hashq.put([ptop, rd, fn]) self.hashq.put([ptop, rd, fn, ip, at])
self.n_hashq += 1 self.n_hashq += 1
# self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn)) # self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn))

View File

@@ -1063,6 +1063,9 @@ def statdir(logger, scandir, lstat, top):
def rmdirs(logger, scandir, lstat, top): def rmdirs(logger, scandir, lstat, top):
if not os.path.exists(fsenc(top)) or not os.path.isdir(fsenc(top)):
top = os.path.dirname(top)
dirs = statdir(logger, scandir, lstat, top) dirs = statdir(logger, scandir, lstat, top)
dirs = [x[0] for x in dirs if stat.S_ISDIR(x[1].st_mode)] dirs = [x[0] for x in dirs if stat.S_ISDIR(x[1].st_mode)]
dirs = [os.path.join(top, x) for x in dirs] dirs = [os.path.join(top, x) for x in dirs]

View File

@@ -49,7 +49,7 @@ pre, code, tt {
transition: opacity 0.14s, height 0.14s, padding 0.14s; transition: opacity 0.14s, height 0.14s, padding 0.14s;
} }
#toast { #toast {
top: 1.4em; bottom: 5em;
right: -1em; right: -1em;
line-height: 1.5em; line-height: 1.5em;
padding: 1em 1.3em; padding: 1em 1.3em;
@@ -78,6 +78,9 @@ pre, code, tt {
border-radius: .5em 0 0 .5em; border-radius: .5em 0 0 .5em;
transition: left .3s, width .3s, padding .3s, opacity .3s; transition: left .3s, width .3s, padding .3s, opacity .3s;
} }
#toast pre {
margin: 0;
}
#toast.vis { #toast.vis {
right: 1.3em; right: 1.3em;
transform: unset; transform: unset;
@@ -952,7 +955,8 @@ input.eq_gain {
color: #300; color: #300;
background: #fea; background: #fea;
} }
.opwide { .opwide,
#op_unpost {
max-width: none; max-width: none;
margin-right: 1.5em; margin-right: 1.5em;
} }
@@ -1054,6 +1058,53 @@ html.light #ggrid a:hover {
color: #015; color: #015;
box-shadow: 0 .1em .5em #aaa; box-shadow: 0 .1em .5em #aaa;
} }
#op_unpost {
padding: 1em;
}
#op_unpost td {
padding: .2em .4em;
}
#op_unpost a {
margin: 0;
padding: 0;
}
#rui {
position: fixed;
top: 0;
left: 0;
width: calc(100% - 2em);
height: auto;
overflow: auto;
max-height: calc(100% - 2em);
border-bottom: .5em solid #999;
background: #333;
padding: 1em;
z-index: 765;
}
#rui div+div {
margin-top: 1em;
}
#rui table {
width: 100%;
}
#rui td {
padding: .2em .5em;
}
#rui td+td,
#rui td input {
width: 100%;
}
#rui input[readonly] {
color: #fff;
background: #444;
border: 1px solid #777;
padding: .2em .25em;
}
#rui h1 {
margin: 0 0 .3em 0;
padding: 0;
font-size: 1.5em;
}
#pvol, #pvol,
#barbuf, #barbuf,
#barpos, #barpos,

View File

@@ -59,6 +59,8 @@
</form> </form>
</div> </div>
<div id="op_unpost" class="opview opbox"></div>
<div id="op_up2k" class="opview"></div> <div id="op_up2k" class="opview"></div>
<div id="op_cfg" class="opview opbox opwide"></div> <div id="op_cfg" class="opview opbox opwide"></div>
@@ -128,6 +130,7 @@
have_tags_idx = {{ have_tags_idx|tojson }}, have_tags_idx = {{ have_tags_idx|tojson }},
have_mv = {{ have_mv|tojson }}, have_mv = {{ have_mv|tojson }},
have_del = {{ have_del|tojson }}, have_del = {{ have_del|tojson }},
have_unpost = {{ have_unpost|tojson }},
have_zip = {{ have_zip|tojson }}; have_zip = {{ have_zip|tojson }};
</script> </script>
<script src="/.cpr/util.js?_={{ ts }}"></script> <script src="/.cpr/util.js?_={{ ts }}"></script>

View File

@@ -12,6 +12,7 @@ ebi('ops').innerHTML = (
'<a href="#" data-dest="" tt="close submenu">---</a>\n' + '<a href="#" data-dest="" tt="close submenu">---</a>\n' +
(have_up2k_idx ? ( (have_up2k_idx ? (
'<a href="#" data-perm="read" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those.$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a>\n' + '<a href="#" data-perm="read" data-dest="search" tt="search for files by attributes, path/name, music tags, or any combination of those.$N$N&lt;code&gt;foo bar&lt;/code&gt; = must contain both foo and bar,$N&lt;code&gt;foo -bar&lt;/code&gt; = must contain foo but not bar,$N&lt;code&gt;^yana .opus$&lt;/code&gt; = must start with yana and have the opus extension">🔎</a>\n' +
(have_del && have_unpost ? '<a href="#" data-dest="unpost" tt="unpost: delete your recent uploads">🧯</a>\n' : '') +
'<a href="#" data-dest="up2k" tt="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>\n' '<a href="#" data-dest="up2k" tt="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>\n'
) : ( ) : (
'<a href="#" data-perm="write" data-dest="up2k" tt="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>\n' '<a href="#" data-perm="write" data-dest="up2k" tt="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>\n'
@@ -132,6 +133,7 @@ ebi('op_cfg').innerHTML = (
' <div>\n' + ' <div>\n' +
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' + ' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔"> tooltips</a>\n' +
' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' + ' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' +
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' + ' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' + ' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
' </div>\n' + ' </div>\n' +
@@ -213,17 +215,6 @@ function goto(dest) {
} }
(function () {
goto();
var op = sread('opmode');
if (op !== null && op !== '.')
try {
goto(op);
}
catch (ex) { }
})();
var have_webp = null; var have_webp = null;
(function () { (function () {
var img = new Image(); var img = new Image();
@@ -531,15 +522,14 @@ var mp = new MPlayer();
makeSortable(ebi('files'), mp.read_order.bind(mp)); makeSortable(ebi('files'), mp.read_order.bind(mp));
function get_np() { function ft2dict(tr) {
var th = ebi('files').tHead.rows[0].cells, var th = ebi('files').tHead.rows[0].cells,
tr = QS('#files tr.play').cells,
rv = [], rv = [],
ra = [], ra = [],
rt = {}; rt = {};
for (var a = 1, aa = th.length; a < aa; a++) { for (var a = 1, aa = th.length; a < aa; a++) {
var tv = tr[a].textContent, var tv = tr.cells[a].textContent,
tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').slice(-1)[0], tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').slice(-1)[0],
vis = th[a].className.indexOf('min') === -1; vis = th[a].className.indexOf('min') === -1;
@@ -550,6 +540,12 @@ function get_np() {
rt[tk] = tv; rt[tk] = tv;
} }
return [rt, rv, ra]; return [rt, rv, ra];
}
function get_np() {
var tr = QS('#files tr.play');
return ft2dict(tr);
}; };
@@ -1478,10 +1474,10 @@ var fileman = (function () {
if (r.clip === null) if (r.clip === null)
r.clip = jread('fman_clip', []); r.clip = jread('fman_clip', []);
var sel = msel.getsel(); var nsel = msel.getsel().length;
clmod(bren, 'en', sel.length == 1); clmod(bren, 'en', nsel == 1);
clmod(bdel, 'en', sel.length); clmod(bdel, 'en', nsel);
clmod(bcut, 'en', sel.length); clmod(bcut, 'en', nsel);
clmod(bpst, 'en', r.clip && r.clip.length); clmod(bpst, 'en', r.clip && r.clip.length);
bren.style.display = have_mv && has(perms, 'write') && has(perms, 'move') ? '' : 'none'; bren.style.display = have_mv && has(perms, 'write') && has(perms, 'move') ? '' : 'none';
bdel.style.display = have_del && has(perms, 'delete') ? '' : 'none'; bdel.style.display = have_del && has(perms, 'delete') ? '' : 'none';
@@ -1506,30 +1502,89 @@ var fileman = (function () {
var vsp = vsplit(src), var vsp = vsplit(src),
base = vsp[0], base = vsp[0],
ofn = vsp[1]; ofn = uricom_dec(vsp[1])[0];
var fn = prompt('new filename:', ofn); var rui = ebi('rui');
if (!fn || fn == ofn) if (!rui) {
return toast.warn(1, 'rename aborted'); rui = mknod('div');
rui.setAttribute('id', 'rui');
var dst = base + fn; document.body.appendChild(rui);
function rename_cb() {
if (this.readyState != XMLHttpRequest.DONE)
return;
if (this.status !== 200) {
var msg = this.responseText;
toast.err(9, 'rename failed:\n' + msg);
return;
}
toast.ok(2, 'rename OK');
treectl.goto(get_evpath());
} }
var xhr = new XMLHttpRequest(); var html = [
xhr.open('GET', src + '?move=' + dst, true); '<h1>rename file</h1>',
xhr.onreadystatechange = rename_cb; '<div><table>',
xhr.send(); '<tr><td>old:</td><td><input type="text" id="rn_old" readonly /></td></tr>',
'<tr><td>new:</td><td><input type="text" id="rn_new" /></td></tr>',
'</table></div>',
'<div>',
'<button id="rn_dec">url-decode</button>',
'|',
'<button id="rn_reset">↺ reset</button>',
'<button id="rn_cancel">❌ cancel</button>',
'<button id="rn_apply">✅ apply rename</button>',
'</div>',
'<div><table>'
];
var vars = ft2dict(ebi(sel[0].id).closest('tr')),
keys = vars[1].concat(vars[2]);
vars = vars[0];
for (var a = 0; a < keys.length; a++)
html.push('<tr><td>' + esc(keys[a]) + '</td><td><input type="text" readonly value="' + esc(vars[keys[a]]) + '" /></td></tr>');
html.push('</table></div>');
rui.innerHTML = html.join('\n');
var iold = ebi('rn_old'),
inew = ebi('rn_new');
function rn_reset() {
inew.value = iold.value;
inew.focus();
inew.setSelectionRange(0, inew.value.lastIndexOf('.'), "forward");
}
function rn_cancel() {
rui.parentNode.removeChild(rui);
}
inew.onkeydown = function (e) {
if (e.key == 'Escape')
return rn_cancel();
if (e.key == 'Enter')
return rn_apply();
};
ebi('rn_cancel').onclick = rn_cancel;
ebi('rn_reset').onclick = rn_reset;
ebi('rn_apply').onclick = rn_apply;
ebi('rn_dec').onclick = function () {
inew.value = uricom_dec(inew.value)[0];
};
iold.value = ofn;
rn_reset();
function rn_apply() {
var dst = base + uricom_enc(inew.value, false);
function rename_cb() {
if (this.readyState != XMLHttpRequest.DONE)
return;
if (this.status !== 200) {
var msg = this.responseText;
toast.err(9, 'rename failed:\n' + msg);
return;
}
toast.ok(2, 'rename OK');
treectl.goto(get_evpath());
rn_cancel();
}
var xhr = new XMLHttpRequest();
xhr.open('GET', src + '?move=' + dst, true);
xhr.onreadystatechange = rename_cb;
xhr.send();
};
}; };
r.delete = function (e) { r.delete = function (e) {
@@ -1621,7 +1676,7 @@ var fileman = (function () {
links = QSA('#files tbody td:nth-child(2) a'); links = QSA('#files tbody td:nth-child(2) a');
for (var a = 0, aa = links.length; a < aa; a++) for (var a = 0, aa = links.length; a < aa; a++)
indir.push(links[a].getAttribute('name')); indir.push(vsplit(links[a].getAttribute('href'))[1]);
for (var a = 0; a < r.clip.length; a++) { for (var a = 0; a < r.clip.length; a++) {
var found = false; var found = false;
@@ -1636,12 +1691,12 @@ var fileman = (function () {
} }
if (exists.length) if (exists.length)
alert('these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + exists.join('\n')); alert('these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + uricom_adec(exists).join('\n'));
if (!req.length) if (!req.length)
return; return;
if (!confirm('paste these ' + req.length + ' items here?\n\n' + req.join('\n'))) if (!confirm('paste these ' + req.length + ' items here?\n\n' + uricom_adec(req).join('\n')))
return; return;
function paster() { function paster() {
@@ -1654,7 +1709,7 @@ var fileman = (function () {
r.tx(srcdir); r.tx(srcdir);
return; return;
} }
toast.inf(0, 'pasting ' + (req.length + 1) + ' items\n\n' + vp); toast.inf(0, 'pasting ' + (req.length + 1) + ' items\n\n' + uricom_dec(vp)[0]);
var dst = get_evpath() + vp.split('/').slice(-1)[0]; var dst = get_evpath() + vp.split('/').slice(-1)[0];
@@ -2425,6 +2480,7 @@ var treectl = (function () {
prev_atop = null, prev_atop = null,
prev_winh = null, prev_winh = null,
dyn = bcfg_get('dyntree', true), dyn = bcfg_get('dyntree', true),
dots = bcfg_get('dotfiles', false),
treesz = icfg_get('treesz', 16); treesz = icfg_get('treesz', 16);
treesz = Math.min(Math.max(treesz, 4), 50); treesz = Math.min(Math.max(treesz, 4), 50);
@@ -2543,7 +2599,7 @@ var treectl = (function () {
xhr.dst = dst; xhr.dst = dst;
xhr.rst = rst; xhr.rst = rst;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.open('GET', dst + '?tree=' + top, true); xhr.open('GET', dst + '?tree=' + top + (dots ? '&dots' : ''), true);
xhr.onreadystatechange = recvtree; xhr.onreadystatechange = recvtree;
xhr.send(); xhr.send();
enspin('#tree'); enspin('#tree');
@@ -2647,7 +2703,7 @@ var treectl = (function () {
xhr.top = url; xhr.top = url;
xhr.hpush = hpush; xhr.hpush = hpush;
xhr.ts = Date.now(); xhr.ts = Date.now();
xhr.open('GET', xhr.top + '?ls', true); xhr.open('GET', xhr.top + '?ls' + (dots ? '&dots' : ''), true);
xhr.onreadystatechange = recvls; xhr.onreadystatechange = recvls;
xhr.send(); xhr.send();
if (hpush) if (hpush)
@@ -2784,6 +2840,13 @@ var treectl = (function () {
return ret; return ret;
} }
function tdots(e) {
ev(e);
dots = !dots;
bcfg_set('dotfiles', dots);
treectl.goto(get_evpath());
}
function dyntree(e) { function dyntree(e) {
ev(e); ev(e);
dyn = !dyn; dyn = !dyn;
@@ -2803,6 +2866,7 @@ var treectl = (function () {
ebi('entree').onclick = treectl.entree; ebi('entree').onclick = treectl.entree;
ebi('detree').onclick = treectl.detree; ebi('detree').onclick = treectl.detree;
ebi('dotfiles').onclick = tdots;
ebi('dyntree').onclick = dyntree; ebi('dyntree').onclick = dyntree;
ebi('twig').onclick = scaletree; ebi('twig').onclick = scaletree;
ebi('twobytwo').onclick = scaletree; ebi('twobytwo').onclick = scaletree;
@@ -2849,7 +2913,7 @@ function apply_perms(newperms) {
var axs = [], var axs = [],
aclass = '>', aclass = '>',
chk = ['read', 'write', 'rename', 'delete']; chk = ['read', 'write', 'move', 'delete'];
for (var a = 0; a < chk.length; a++) for (var a = 0; a < chk.length; a++)
if (has(perms, chk[a])) if (has(perms, chk[a]))
@@ -3329,13 +3393,11 @@ var msel = (function () {
item.id = links[a].getAttribute('id'); item.id = links[a].getAttribute('id');
item.sel = links[a].closest('tr').classList.contains('sel'); item.sel = links[a].closest('tr').classList.contains('sel');
item.vp = href.indexOf('/') !== -1 ? href : vbase + href; item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
item.name = href.split('/').slice(-1);
r.all.push(item); r.all.push(item);
if (item.sel) if (item.sel)
r.sel.push(item); r.sel.push(item);
links[a].setAttribute('name', item.name);
links[a].closest('tr').setAttribute('tabindex', '0'); links[a].closest('tr').setAttribute('tabindex', '0');
} }
}; };
@@ -3375,10 +3437,15 @@ var msel = (function () {
}; };
ebi('selzip').onclick = function (e) { ebi('selzip').onclick = function (e) {
ev(e); ev(e);
var names = r.getsel(), var sel = r.getsel(),
arg = ebi('selzip').getAttribute('fmt'), arg = ebi('selzip').getAttribute('fmt'),
txt = names.join('\n'), frm = mknod('form'),
frm = mknod('form'); txt = [];
for (var a = 0; a < sel.length; a++)
txt.push(vsplit(sel[a].vp)[1]);
txt = txt.join('\n');
frm.setAttribute('action', '?' + arg); frm.setAttribute('action', '?' + arg);
frm.setAttribute('method', 'post'); frm.setAttribute('method', 'post');
@@ -3435,6 +3502,160 @@ function ev_row_tgl(e) {
} }
var unpost = (function () {
ebi('op_unpost').innerHTML = (
"you can delete your recent uploads below &ndash; click the fire-extinguisher icon to refresh" +
'<p>optional filter:&nbsp; URL must contain <input type="text" id="unpost_filt" size="20" /><a id="unpost_nofilt" href="#">clear filter</a></p>' +
'<div id="unpost"></div>'
);
var r = {},
ct = ebi('unpost'),
filt = ebi('unpost_filt');
r.files = [];
r.me = null;
r.load = function () {
var me = Date.now(),
html = [];
function unpost_load_cb() {
if (this.readyState != XMLHttpRequest.DONE)
return;
if (this.status !== 200) {
var msg = this.responseText;
toast.err(9, 'unpost-load failed:\n' + msg);
ebi('op_unpost').innerHTML = html.join('\n');
return;
}
var res = JSON.parse(this.responseText);
if (res.length) {
if (res.length == 2000)
html.push("<p>showing first 2000 files (use the filter)");
else
html.push("<p>" + res.length + " uploads can be deleted");
html.push(" &ndash; sorted by upload time &ndash; most recent first:</p>");
html.push("<table><thead><tr><td></td><td>time</td><td>size</td><td>file</td></tr></thead><tbody>");
}
else
html.push("<p>sike! no uploads " + (filt.value ? 'matching that filter' : '') + " are sufficiently recent</p>");
var mods = [1000, 100, 10];
for (var a = 0; a < res.length; a++) {
for (var b = 0; b < mods.length; b++)
if (a % mods[b] == 0 && res.length > a + mods[b] / 10)
html.push(
'<tr><td></td><td colspan="3" style="padding:.5em">' +
'<a me="' + me + '" class="n' + a + '" n2="' + (a + mods[b]) +
'" href="#">delete the next ' + Math.min(mods[b], res.length - a) + ' files below</a></td></tr>');
html.push(
'<tr><td><a me="' + me + '" class="n' + a + '" href="#">delete</a></td>' +
'<td>' + unix2iso(res[a].at) + '</td>' +
'<td>' + res[a].sz + '</td>' +
'<td>' + linksplit(res[a].vp).join(' ') + '</td></tr>');
}
html.push("</tbody></table>");
ct.innerHTML = html.join('\n');
r.files = res;
r.me = me;
}
var q = '/?ups';
if (filt.value)
q += '&filter=' + uricom_enc(filt.value, true);
var xhr = new XMLHttpRequest();
xhr.open('GET', q, true);
xhr.onreadystatechange = unpost_load_cb;
xhr.send();
ct.innerHTML = "<p><em>loading your recent uploads...</em></p>";
};
function unpost_delete_cb() {
if (this.readyState != XMLHttpRequest.DONE)
return;
if (this.status !== 200) {
var msg = this.responseText;
toast.err(9, 'unpost-delete failed:\n' + msg);
return;
}
for (var a = this.n; a < this.n2; a++) {
var o = QSA('#op_unpost a.n' + a);
for (var b = 0; b < o.length; b++) {
var o2 = o[b].closest('tr');
o2.parentNode.removeChild(o2);
}
}
toast.ok(5, this.responseText);
if (!QS('#op_unpost a[me]'))
ebi(goto_unpost());
}
ct.onclick = function (e) {
var tgt = e.target.closest('a[me]');
if (!tgt)
return;
if (!tgt.getAttribute('href'))
return;
var ame = tgt.getAttribute('me');
if (ame != r.me)
return toast.err(0, 'something broke, please try a refresh');
var n = parseInt(tgt.className.slice(1)),
n2 = parseInt(tgt.getAttribute('n2') || n + 1),
req = [];
for (var a = n; a < n2; a++)
if (QS('#op_unpost a.n' + a))
req.push(r.files[a].vp);
var links = QSA('#op_unpost a.n' + n);
for (var a = 0, aa = links.length; a < aa; a++) {
links[a].removeAttribute('href');
links[a].innerHTML = '[busy]';
}
toast.inf(0, "deleting " + req.length + " files...");
var xhr = new XMLHttpRequest();
xhr.n = n;
xhr.n2 = n2;
xhr.open('POST', '/?delete', true);
xhr.onreadystatechange = unpost_delete_cb;
xhr.send(JSON.stringify(req));
};
var tfilt = null;
filt.oninput = function () {
clearTimeout(tfilt);
tfilt = setTimeout(r.load, 250);
};
ebi('unpost_nofilt').onclick = function () {
filt.value = '';
r.load();
};
return r;
})();
function goto_unpost(e) {
unpost.load();
}
function reload_mp() { function reload_mp() {
if (mp && mp.au) { if (mp && mp.au) {
mp.au.pause(); mp.au.pause();

View File

@@ -41,6 +41,9 @@ html, body {
text-shadow: 1px 1px 0 #000; text-shadow: 1px 1px 0 #000;
color: #fff; color: #fff;
} }
#toast pre {
margin: 0;
}
#toastc { #toastc {
display: inline-block; display: inline-block;
position: absolute; position: absolute;

View File

@@ -285,15 +285,15 @@ function Modpoll() {
console.log("modpoll diff |" + server_ref.length + "|, |" + server_now.length + "|"); console.log("modpoll diff |" + server_ref.length + "|, |" + server_now.length + "|");
this.modpoll.disabled = true; this.modpoll.disabled = true;
var msg = [ var msg = [
"The document has changed on the server.<br />" + "The document has changed on the server.",
"The changes will NOT be loaded into your editor automatically.", "The changes will NOT be loaded into your editor automatically.",
"",
"Press F5 or CTRL-R to refresh the page,<br />" + "Press F5 or CTRL-R to refresh the page,",
"replacing your document with the server copy.", "replacing your document with the server copy.",
"",
"You can close this message to ignore and contnue." "You can close this message to ignore and contnue."
]; ];
return toast.warn(0, "<p>" + msg.join('</p>\n<p>') + '</p>'); return toast.warn(0, msg.join('\n'));
} }
console.log('modpoll eq'); console.log('modpoll eq');

View File

@@ -75,7 +75,7 @@ function set_jumpto() {
} }
function jumpto(ev) { function jumpto(ev) {
var tgt = ev.target || ev.srcElement; var tgt = ev.target;
var ln = null; var ln = null;
while (tgt && !ln) { while (tgt && !ln) {
ln = tgt.getAttribute('data-ln'); ln = tgt.getAttribute('data-ln');

View File

@@ -1773,3 +1773,14 @@ if (QS('#op_up2k.act'))
goto_up2k(); goto_up2k();
apply_perms(perms); apply_perms(perms);
(function () {
goto();
var op = sread('opmode');
if (op !== null && op !== '.')
try {
goto(op);
}
catch (ex) { }
})();

View File

@@ -398,6 +398,15 @@ function uricom_dec(txt) {
} }
function uricom_adec(arr) {
var ret = [];
for (var a = 0; a < arr.length; a++)
ret.push(uricom_dec(arr[a])[0]);
return ret;
}
function get_evpath() { function get_evpath() {
var ret = document.location.pathname; var ret = document.location.pathname;

View File

@@ -44,7 +44,7 @@ avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} c
dirs=("$HOME/vfs/ほげ" "$HOME/vfs/ほげ/ぴよ" "$HOME/vfs/$(printf \\xed\\x91)" "$HOME/vfs/$(printf \\xed\\x91/\\xed\\x92)") dirs=("$HOME/vfs/ほげ" "$HOME/vfs/ほげ/ぴよ" "$HOME/vfs/$(printf \\xed\\x91)" "$HOME/vfs/$(printf \\xed\\x91/\\xed\\x92)")
mkdir -p "${dirs[@]}" mkdir -p "${dirs[@]}"
for dir in "${dirs[@]}"; do for fn in ふが "$(printf \\xed\\x93)" 'qwe,rty;asd fgh+jkl%zxc&vbn <qwe>"rty'"'"'uio&asd&nbsp;fgh'; do echo "$dir" > "$dir/$fn.html"; done; done for dir in "${dirs[@]}"; do for fn in ふが "$(printf \\xed\\x93)" 'qwe,rty;asd fgh+jkl%zxc&vbn <qwe>"rty'"'"'uio&asd&nbsp;fgh'; do echo "$dir" > "$dir/$fn.html"; done; done
# qw er+ty%20ui%%20op<as>df&gh&amp;jk#zx'cv"bn`m=qw*er^ty?ui@op,as.df-gh_jk
## ##
## upload mojibake ## upload mojibake
@@ -79,6 +79,10 @@ command -v gdate && date() { gdate "$@"; }; while true; do t=$(date +%s.%N); (ti
# get all up2k search result URLs # get all up2k search result URLs
var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.querySelectorAll('#u2tab .prog a').forEach((x) => {t.push(b+encodeURI(x.getAttribute("href")))}); console.log(t.join("\n")); var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.querySelectorAll('#u2tab .prog a').forEach((x) => {t.push(b+encodeURI(x.getAttribute("href")))}); console.log(t.join("\n"));
# rename all selected songs to <leading-track-number> + <Title> + <extension>
var sel=msel.getsel(), ci=find_file_col('Title')[0], re=[]; for (var a=0; a<sel.length; a++) { var url=sel[a].vp, tag=ebi(sel[a].id).closest('tr').querySelectorAll('td')[ci].textContent, name=uricom_dec(vsplit(url)[1])[0], m=/^([0-9]+[\. -]+)?.*(\.[^\.]+$)/.exec(name), name2=(m[1]||'')+tag+m[2], url2=vsplit(url)[0]+uricom_enc(name2,false); if (url!=url2) re.push([url, url2]); }
console.log(JSON.stringify(re, null, ' '));
function f() { if (!re.length) return treectl.goto(get_evpath()); var [u1,u2] = re.shift(); fetch(u1+'?move='+u2).then((rsp) => {if (rsp.ok) f(); }); }; f();
## ##
## bash oneliners ## bash oneliners

View File

@@ -34,6 +34,7 @@ gtar=$(command -v gtar || command -v gnutar) || true
sed() { gsed "$@"; } sed() { gsed "$@"; }
find() { gfind "$@"; } find() { gfind "$@"; }
sort() { gsort "$@"; } sort() { gsort "$@"; }
sha1sum() { shasum "$@"; }
unexpand() { gunexpand "$@"; } unexpand() { gunexpand "$@"; }
command -v grealpath >/dev/null && command -v grealpath >/dev/null &&
realpath() { grealpath "$@"; } realpath() { grealpath "$@"; }
@@ -81,16 +82,23 @@ tmv() {
mv t "$1" mv t "$1"
} }
stamp=$(
for d in copyparty scripts; do
find $d -type f -printf '%TY-%Tm-%Td %TH:%TM:%TS %p\n'
done | sort | tail -n 1 | sha1sum | cut -c-16
)
rm -rf sfx/* rm -rf sfx/*
mkdir -p sfx build mkdir -p sfx build
cd sfx cd sfx
[ $repack ] && { tmpdir="$(
old="$( printf '%s\n' "$TMPDIR" /tmp |
printf '%s\n' "$TMPDIR" /tmp | awk '/./ {print; exit}'
awk '/./ {print; exit}' )"
)/pe-copyparty"
[ $repack ] && {
old="$tmpdir/pe-copyparty"
echo "repack of files in $old" echo "repack of files in $old"
cp -pR "$old/"*{dep-j2,copyparty} . cp -pR "$old/"*{dep-j2,copyparty} .
} }
@@ -172,12 +180,12 @@ mkdir -p ../dist
sfx_out=../dist/copyparty-sfx sfx_out=../dist/copyparty-sfx
echo cleanup echo cleanup
find .. -name '*.pyc' -delete find -name '*.pyc' -delete
find .. -name __pycache__ -delete find -name __pycache__ -delete
# especially prevent osx from leaking your lan ip (wtf apple) # especially prevent osx from leaking your lan ip (wtf apple)
find .. -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete find -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done find -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
echo use smol web deps echo use smol web deps
rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
@@ -241,20 +249,42 @@ find | grep -E '\.(js|html)$' | while IFS= read -r f; do
tmv "$f" tmv "$f"
done done
gzres() { gzres() {
command -v pigz && command -v pigz &&
pk='pigz -11 -J 34 -I 100' || pk='pigz -11 -J 34 -I 256' ||
pk='gzip' pk='gzip'
echo "$pk" echo "$pk"
find | grep -E '\.(js|css)$' | grep -vF /deps/ | while IFS= read -r f; do find | grep -E '\.(js|css)$' | grep -vF /deps/ | while IFS= read -r f; do
echo -n . echo -n .
$pk "$f" $pk "$f"
done done
echo echo
}
zdir="$tmpdir/cpp-mksfx"
[ -e "$zdir/$stamp" ] || rm -rf "$zdir"
mkdir -p "$zdir"
echo a > "$zdir/$stamp"
nf=$(ls -1 "$zdir"/arc.* | wc -l)
[ $nf -ge 2 ] && [ ! $repack ] && use_zdir=1 || use_zdir=
[ $use_zdir ] || {
echo "$nf alts += 1"
gzres
[ $repack ] ||
tar -cf "$zdir/arc.$(date +%s)" copyparty/web/*.gz
}
[ $use_zdir ] && {
arcs=("$zdir"/arc.*)
arc="${arcs[$RANDOM % ${#arcs[@]} ] }"
echo "using $arc"
tar -xf "$arc"
for f in copyparty/web/*.gz; do
rm "${f%.*}"
done
} }
gzres
echo gen tarlist echo gen tarlist

View File

@@ -65,9 +65,9 @@ def uncomment(fpath):
def main(): def main():
print("uncommenting", end="") print("uncommenting", end="", flush=True)
for f in sys.argv[1:]: for f in sys.argv[1:]:
print(".", end="") print(".", end="", flush=True)
uncomment(f) uncomment(f)
print("k") print("k")

View File

@@ -31,6 +31,7 @@ class Cfg(Namespace):
rproxy=0, rproxy=0,
ed=False, ed=False,
nw=False, nw=False,
unpost=600,
no_mv=False, no_mv=False,
no_del=False, no_del=False,
no_zip=False, no_zip=False,