mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
add event hooks
This commit is contained in:
parent
70f1642d0d
commit
f8e3e87a52
30
README.md
30
README.md
|
@ -1,6 +1,6 @@
|
||||||
# ⇆🎉 copyparty
|
# ⇆🎉 copyparty
|
||||||
|
|
||||||
* http file sharing hub (py2/py3) [(on PyPI)](https://pypi.org/project/copyparty/)
|
* portable file sharing hub (py2/py3) [(on PyPI)](https://pypi.org/project/copyparty/)
|
||||||
* MIT-Licensed, 2019-05-26, ed @ irc.rizon.net
|
* MIT-Licensed, 2019-05-26, ed @ irc.rizon.net
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +75,8 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
|
||||||
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
|
* [database location](#database-location) - in-volume (`.hist/up2k.db`, default) or somewhere else
|
||||||
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
* [metadata from audio files](#metadata-from-audio-files) - set `-e2t` to index tags on upload
|
||||||
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags
|
* [file parser plugins](#file-parser-plugins) - provide custom parsers to index additional tags
|
||||||
* [upload events](#upload-events) - trigger a script/program on each upload
|
* [event hooks](#event-hooks) - trigger a script/program on uploads, renames etc
|
||||||
|
* [upload events](#upload-events) - the older, more powerful approach
|
||||||
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
||||||
* [themes](#themes)
|
* [themes](#themes)
|
||||||
* [complete examples](#complete-examples)
|
* [complete examples](#complete-examples)
|
||||||
|
@ -163,6 +164,7 @@ recommended additional steps on debian which enable audio metadata and thumbnai
|
||||||
* upload
|
* upload
|
||||||
* ☑ basic: plain multipart, ie6 support
|
* ☑ basic: plain multipart, ie6 support
|
||||||
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
||||||
|
* not affected by cloudflare's max-upload-size (100 MiB)
|
||||||
* ☑ stash: simple PUT filedropper
|
* ☑ stash: simple PUT filedropper
|
||||||
* ☑ [unpost](#unpost): undo/delete accidental uploads
|
* ☑ [unpost](#unpost): undo/delete accidental uploads
|
||||||
* ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
|
* ☑ [self-destruct](#self-destruct) (specified server-side or client-side)
|
||||||
|
@ -924,6 +926,8 @@ some examples,
|
||||||
## other flags
|
## other flags
|
||||||
|
|
||||||
* `:c,magic` enables filetype detection for nameless uploads, same as `--magic`
|
* `:c,magic` enables filetype detection for nameless uploads, same as `--magic`
|
||||||
|
* needs https://pypi.org/project/python-magic/ `python3 -m pip install --user -U python-magic`
|
||||||
|
* on windows grab this instead `python3 -m pip install --user -U python-magic-bin`
|
||||||
|
|
||||||
|
|
||||||
## database location
|
## database location
|
||||||
|
@ -992,9 +996,18 @@ copyparty can invoke external programs to collect additional metadata for files
|
||||||
if something doesn't work, try `--mtag-v` for verbose error messages
|
if something doesn't work, try `--mtag-v` for verbose error messages
|
||||||
|
|
||||||
|
|
||||||
## upload events
|
## event hooks
|
||||||
|
|
||||||
trigger a script/program on each upload like so:
|
trigger a script/program on uploads, renames etc
|
||||||
|
|
||||||
|
you can set hooks before and/or after an event happens, and currently you can hook uploads, moves/renames, and deletes
|
||||||
|
|
||||||
|
there's a bunch of flags and stuff, see `--help-hooks`
|
||||||
|
|
||||||
|
|
||||||
|
### upload events
|
||||||
|
|
||||||
|
the older, more powerful approach:
|
||||||
|
|
||||||
```
|
```
|
||||||
-v /mnt/inc:inc:w:c,mte=+x1:c,mtp=x1=ad,kn,/usr/bin/notify-send
|
-v /mnt/inc:inc:w:c,mte=+x1:c,mtp=x1=ad,kn,/usr/bin/notify-send
|
||||||
|
@ -1004,11 +1017,12 @@ so filesystem location `/mnt/inc` shared at `/inc`, write-only for everyone, app
|
||||||
|
|
||||||
that'll run the command `notify-send` with the path to the uploaded file as the first and only argument (so on linux it'll show a notification on-screen)
|
that'll run the command `notify-send` with the path to the uploaded file as the first and only argument (so on linux it'll show a notification on-screen)
|
||||||
|
|
||||||
note that it will only trigger on new unique files, not dupes
|
note that this is way more complicated than the new [event hooks](#event-hooks) but this approach has the following advantages:
|
||||||
|
* non-blocking and multithreaded; doesn't hold other uploads back
|
||||||
|
* you get access to tags from FFmpeg and other mtp parsers
|
||||||
|
* only trigger on new unique files, not dupes
|
||||||
|
|
||||||
and it will occupy the parsing threads, so fork anything expensive (or set `kn` to have copyparty fork it for you) -- otoh if you want to intentionally queue/singlethread you can combine it with `--mtag-mt 1`
|
note that it will occupy the parsing threads, so fork anything expensive (or set `kn` to have copyparty fork it for you) -- otoh if you want to intentionally queue/singlethread you can combine it with `--mtag-mt 1`
|
||||||
|
|
||||||
if this becomes popular maybe there should be a less janky way to do it actually
|
|
||||||
|
|
||||||
|
|
||||||
## hiding from google
|
## hiding from google
|
||||||
|
|
61
bin/hooks/discord-announce.py
Normal file
61
bin/hooks/discord-announce.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from copyparty.util import humansize, quotep
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
announces a new upload on discord
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xau f,t5,j,bin/hooks/discord-announce.py
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:c,xau=f,t5,j,bin/hooks/discord-announce.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
f = fork; don't wait for it to finish
|
||||||
|
t5 = timeout if it's still running after 5 sec
|
||||||
|
j = provide upload information as json; not just the filename
|
||||||
|
|
||||||
|
replace "xau" with "xbu" to announce Before upload starts instead of After completion
|
||||||
|
|
||||||
|
# how to discord:
|
||||||
|
first create the webhook url; https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
|
||||||
|
then use this to design your message: https://discohook.org/
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
WEBHOOK = "https://discord.com/api/webhooks/1234/base64"
|
||||||
|
|
||||||
|
# read info from copyparty
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
vpath = inf["vp"]
|
||||||
|
filename = vpath.split("/")[-1]
|
||||||
|
url = f"https://{inf['host']}/{quotep(vpath)}"
|
||||||
|
|
||||||
|
# compose the message to discord
|
||||||
|
j = {
|
||||||
|
"title": filename,
|
||||||
|
"url": url,
|
||||||
|
"description": url.rsplit("/", 1)[0],
|
||||||
|
"color": 0x449900,
|
||||||
|
"fields": [
|
||||||
|
{"name": "Size", "value": humansize(inf["sz"])},
|
||||||
|
{"name": "User", "value": inf["user"]},
|
||||||
|
{"name": "IP", "value": inf["ip"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
for v in j["fields"]:
|
||||||
|
v["inline"] = True
|
||||||
|
|
||||||
|
r = requests.post(WEBHOOK, json={"embeds": [j]})
|
||||||
|
print(f"discord: {r}\n", end="")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
30
bin/hooks/notify.py
Normal file
30
bin/hooks/notify.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from plyer import notification
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
show os notification on upload; works on windows, linux, macos
|
||||||
|
|
||||||
|
depdencies:
|
||||||
|
python3 -m pip install --user -U plyer
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xau f,bin/hooks/notify.py
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:c,xau=f,bin/hooks/notify.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xau = execute after upload
|
||||||
|
f = fork so it doesn't block uploads
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
notification.notify(title="new file uploaded", message=sys.argv[1], timeout=10)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
30
bin/hooks/reject-extension.py
Normal file
30
bin/hooks/reject-extension.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
reject file uploads by file extension
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xbu c,bin/hooks/reject-extension.py
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:c,xbu=c,bin/hooks/reject-extension.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xbu = execute before upload
|
||||||
|
c = check result, reject upload if error
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
bad = "exe scr com pif bat ps1 jar msi"
|
||||||
|
|
||||||
|
ext = sys.argv[1].split(".")[-1]
|
||||||
|
|
||||||
|
sys.exit(1 if ext in bad.split() else 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
39
bin/hooks/reject-mimetype.py
Normal file
39
bin/hooks/reject-mimetype.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import magic
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
reject file uploads by mimetype
|
||||||
|
|
||||||
|
dependencies (linux, macos):
|
||||||
|
python3 -m pip install --user -U python-magic
|
||||||
|
|
||||||
|
dependencies (windows):
|
||||||
|
python3 -m pip install --user -U python-magic-bin
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xau c,bin/hooks/reject-mimetype.py
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:c,xau=c,bin/hooks/reject-mimetype.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
xau = execute after upload
|
||||||
|
c = check result, reject upload if error
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ok = ["image/jpeg", "image/png"]
|
||||||
|
|
||||||
|
mt = magic.from_file(sys.argv[1], mime=True)
|
||||||
|
|
||||||
|
print(mt)
|
||||||
|
|
||||||
|
sys.exit(1 if mt not in ok else 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
54
bin/hooks/wget.py
Normal file
54
bin/hooks/wget.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
|
||||||
|
_ = r"""
|
||||||
|
use copyparty as a file downloader by POSTing URLs as
|
||||||
|
application/x-www-form-urlencoded (for example using the
|
||||||
|
message/pager function on the website)
|
||||||
|
|
||||||
|
example usage as global config:
|
||||||
|
--xm f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
|
example usage as a volflag (per-volume config):
|
||||||
|
-v srv/inc:inc:c,xm=f,j,t3600,bin/hooks/wget.py
|
||||||
|
|
||||||
|
parameters explained,
|
||||||
|
f = fork so it doesn't block uploads
|
||||||
|
j = provide message information as json; not just the text
|
||||||
|
c3 = mute all output
|
||||||
|
t3600 = timeout and kill download after 1 hour
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
inf = json.loads(sys.argv[1])
|
||||||
|
url = inf["txt"]
|
||||||
|
if "://" not in url:
|
||||||
|
url = "https://" + url
|
||||||
|
|
||||||
|
os.chdir(inf["ap"])
|
||||||
|
|
||||||
|
name = url.split("?")[0].split("/")[-1]
|
||||||
|
tfn = "-- DOWNLOADING " + name
|
||||||
|
print(f"{tfn}\n", end="")
|
||||||
|
open(tfn, "wb").close()
|
||||||
|
|
||||||
|
cmd = ["wget", "--trust-server-names", "-nv", "--", url]
|
||||||
|
|
||||||
|
try:
|
||||||
|
sp.check_call(cmd)
|
||||||
|
except:
|
||||||
|
t = "-- FAILED TO DONWLOAD " + name
|
||||||
|
print(f"{t}\n", end="")
|
||||||
|
open(t, "wb").close()
|
||||||
|
|
||||||
|
os.unlink(tfn)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -555,6 +555,44 @@ def get_sects():
|
||||||
\033[0m"""
|
\033[0m"""
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"hooks",
|
||||||
|
"execute commands before/after various events",
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
execute a command (a program or script) before or after various events;
|
||||||
|
\033[36mxbu\033[35m executes CMD before a file upload starts
|
||||||
|
\033[36mxau\033[35m executes CMD after a file upload finishes
|
||||||
|
\033[36mxbr\033[35m executes CMD before a file rename/move
|
||||||
|
\033[36mxar\033[35m executes CMD after a file rename/move
|
||||||
|
\033[36mxbd\033[35m executes CMD before a file delete
|
||||||
|
\033[36mxad\033[35m executes CMD after a file delete
|
||||||
|
\033[36mxm\033[35m executes CMD on message
|
||||||
|
\033[0m
|
||||||
|
can be defined as --args or volflags; for example \033[36m
|
||||||
|
--xau notify-send
|
||||||
|
-v .::r:c,xau=notify-send
|
||||||
|
\033[0m
|
||||||
|
commands specified as --args are appended to volflags;
|
||||||
|
each --arg and volflag can be specified multiple times,
|
||||||
|
each command will execute in order unless one returns non-zero
|
||||||
|
|
||||||
|
optionally prefix the command with comma-sep. flags similar to -mtp:
|
||||||
|
\033[36mf\033[35m forks the process, doesn't wait for completion
|
||||||
|
\033[36mc\033[35m checks return code, blocks the action if non-zero
|
||||||
|
\033[36mj\033[35m provides json with info as 1st arg instead of filepath
|
||||||
|
\033[36mwN\033[35m waits N sec after command has been started before continuing
|
||||||
|
\033[36mtN\033[35m sets an N sec timeout before the command is abandoned
|
||||||
|
\033[36mkt\033[35m kills the entire process tree on timeout (default),
|
||||||
|
\033[36mkm\033[35m kills just the main process
|
||||||
|
\033[36mkn\033[35m lets it continue running until copyparty is terminated
|
||||||
|
\033[36mc0\033[35m show all process output (default)
|
||||||
|
\033[36mc1\033[35m show only stderr
|
||||||
|
\033[36mc2\033[35m show only stdout
|
||||||
|
\033[36mc3\033[35m mute all process otput
|
||||||
|
\033[0m"""
|
||||||
|
),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"urlform",
|
"urlform",
|
||||||
"how to handle url-form POSTs",
|
"how to handle url-form POSTs",
|
||||||
|
@ -758,6 +796,17 @@ def add_smb(ap):
|
||||||
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
||||||
|
|
||||||
|
|
||||||
|
def add_hooks(ap):
|
||||||
|
ap2 = ap.add_argument_group('hooks (see --help-hooks)')
|
||||||
|
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute CMD before a file upload starts")
|
||||||
|
ap2.add_argument("--xau", metavar="CMD", type=u, action="append", help="execute CMD after a file upload finishes")
|
||||||
|
ap2.add_argument("--xbr", metavar="CMD", type=u, action="append", help="execute CMD before a file move/rename")
|
||||||
|
ap2.add_argument("--xar", metavar="CMD", type=u, action="append", help="execute CMD after a file move/rename")
|
||||||
|
ap2.add_argument("--xbd", metavar="CMD", type=u, action="append", help="execute CMD before a file delete")
|
||||||
|
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute CMD after a file delete")
|
||||||
|
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute CMD on message")
|
||||||
|
|
||||||
|
|
||||||
def add_optouts(ap):
|
def add_optouts(ap):
|
||||||
ap2 = ap.add_argument_group('opt-outs')
|
ap2 = ap.add_argument_group('opt-outs')
|
||||||
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
|
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
|
||||||
|
@ -967,6 +1016,7 @@ def run_argparse(
|
||||||
add_webdav(ap)
|
add_webdav(ap)
|
||||||
add_smb(ap)
|
add_smb(ap)
|
||||||
add_safety(ap, fk_salt)
|
add_safety(ap, fk_salt)
|
||||||
|
add_hooks(ap)
|
||||||
add_optouts(ap)
|
add_optouts(ap)
|
||||||
add_shutdown(ap)
|
add_shutdown(ap)
|
||||||
add_ui(ap, retry)
|
add_ui(ap, retry)
|
||||||
|
|
|
@ -812,7 +812,7 @@ class AuthSrv(object):
|
||||||
value: Union[str, bool, list[str]],
|
value: Union[str, bool, list[str]],
|
||||||
is_list: bool,
|
is_list: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
if name not in ["mtp"]:
|
if name not in ["mtp", "xbu", "xau", "xbr", "xar", "xbd", "xad", "xm"]:
|
||||||
flags[name] = value
|
flags[name] = value
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1151,8 +1151,9 @@ class AuthSrv(object):
|
||||||
if "mth" not in vol.flags:
|
if "mth" not in vol.flags:
|
||||||
vol.flags["mth"] = self.args.mth
|
vol.flags["mth"] = self.args.mth
|
||||||
|
|
||||||
# append parsers from argv to volflags
|
# append additive args from argv to volflags
|
||||||
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
for name in ["mtp", "xbu", "xau", "xbr", "xar", "xbd", "xad", "xm"]:
|
||||||
|
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
||||||
|
|
||||||
# d2d drops all database features for a volume
|
# d2d drops all database features for a volume
|
||||||
for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
|
for grp, rm in [["d2d", "e2d"], ["d2t", "e2t"], ["d2d", "e2v"]]:
|
||||||
|
|
|
@ -63,6 +63,7 @@ from .util import (
|
||||||
read_socket_unbounded,
|
read_socket_unbounded,
|
||||||
relchk,
|
relchk,
|
||||||
ren_open,
|
ren_open,
|
||||||
|
runhook,
|
||||||
hidedir,
|
hidedir,
|
||||||
s3enc,
|
s3enc,
|
||||||
sanitize_fn,
|
sanitize_fn,
|
||||||
|
@ -1189,9 +1190,27 @@ class HttpCli(object):
|
||||||
plain = zb.decode("utf-8", "replace")
|
plain = zb.decode("utf-8", "replace")
|
||||||
if buf.startswith(b"msg="):
|
if buf.startswith(b"msg="):
|
||||||
plain = plain[4:]
|
plain = plain[4:]
|
||||||
|
vfs, rem = self.asrv.vfs.get(
|
||||||
|
self.vpath, self.uname, False, False
|
||||||
|
)
|
||||||
|
xm = vfs.flags.get("xm")
|
||||||
|
if xm:
|
||||||
|
runhook(
|
||||||
|
self.log,
|
||||||
|
xm,
|
||||||
|
vfs.canonical(rem),
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.ip,
|
||||||
|
time.time(),
|
||||||
|
len(xm),
|
||||||
|
plain,
|
||||||
|
)
|
||||||
|
|
||||||
t = "urlform_dec {} @ {}\n {}\n"
|
t = "urlform_dec {} @ {}\n {}\n"
|
||||||
self.log(t.format(len(plain), self.vpath, plain))
|
self.log(t.format(len(plain), self.vpath, plain))
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.log(repr(ex))
|
self.log(repr(ex))
|
||||||
|
|
||||||
|
@ -1232,7 +1251,7 @@ class HttpCli(object):
|
||||||
# post_sz, sha_hex, sha_b64, remains, path, url
|
# post_sz, sha_hex, sha_b64, remains, path, url
|
||||||
reader, remains = self.get_body_reader()
|
reader, remains = self.get_body_reader()
|
||||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||||
rnd, _, lifetime = self.upload_flags(vfs)
|
rnd, _, lifetime, xbu, xau = self.upload_flags(vfs)
|
||||||
lim = vfs.get_dbv(rem)[0].lim
|
lim = vfs.get_dbv(rem)[0].lim
|
||||||
fdir = vfs.canonical(rem)
|
fdir = vfs.canonical(rem)
|
||||||
if lim:
|
if lim:
|
||||||
|
@ -1332,6 +1351,24 @@ class HttpCli(object):
|
||||||
):
|
):
|
||||||
params["overwrite"] = "a"
|
params["overwrite"] = "a"
|
||||||
|
|
||||||
|
if xbu:
|
||||||
|
at = time.time() - lifetime
|
||||||
|
if not runhook(
|
||||||
|
self.log,
|
||||||
|
xbu,
|
||||||
|
path,
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.ip,
|
||||||
|
at,
|
||||||
|
remains,
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "upload denied by xbu"
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
with ren_open(fn, *open_a, **params) as zfw:
|
with ren_open(fn, *open_a, **params) as zfw:
|
||||||
f, fn = zfw["orz"]
|
f, fn = zfw["orz"]
|
||||||
path = os.path.join(fdir, fn)
|
path = os.path.join(fdir, fn)
|
||||||
|
@ -1371,6 +1408,24 @@ class HttpCli(object):
|
||||||
fn = fn2
|
fn = fn2
|
||||||
path = path2
|
path = path2
|
||||||
|
|
||||||
|
at = time.time() - lifetime
|
||||||
|
if xau and not runhook(
|
||||||
|
self.log,
|
||||||
|
xau,
|
||||||
|
path,
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.ip,
|
||||||
|
at,
|
||||||
|
post_sz,
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "upload denied by xau"
|
||||||
|
self.log(t, 1)
|
||||||
|
os.unlink(path)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
vfs, rem = vfs.get_dbv(rem)
|
vfs, rem = vfs.get_dbv(rem)
|
||||||
self.conn.hsrv.broker.say(
|
self.conn.hsrv.broker.say(
|
||||||
"up2k.hash_file",
|
"up2k.hash_file",
|
||||||
|
@ -1379,7 +1434,7 @@ class HttpCli(object):
|
||||||
rem,
|
rem,
|
||||||
fn,
|
fn,
|
||||||
self.ip,
|
self.ip,
|
||||||
time.time() - lifetime,
|
at,
|
||||||
)
|
)
|
||||||
|
|
||||||
vsuf = ""
|
vsuf = ""
|
||||||
|
@ -1572,6 +1627,8 @@ class HttpCli(object):
|
||||||
body["vtop"] = dbv.vpath
|
body["vtop"] = dbv.vpath
|
||||||
body["ptop"] = dbv.realpath
|
body["ptop"] = dbv.realpath
|
||||||
body["prel"] = vrem
|
body["prel"] = vrem
|
||||||
|
body["host"] = self.host
|
||||||
|
body["user"] = self.uname
|
||||||
body["addr"] = self.ip
|
body["addr"] = self.ip
|
||||||
body["vcfg"] = dbv.flags
|
body["vcfg"] = dbv.flags
|
||||||
|
|
||||||
|
@ -1893,7 +1950,7 @@ class HttpCli(object):
|
||||||
self.redirect(vpath, "?edit")
|
self.redirect(vpath, "?edit")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def upload_flags(self, vfs: VFS) -> tuple[int, bool, int]:
|
def upload_flags(self, vfs: VFS) -> tuple[int, bool, int, list[str], list[str]]:
|
||||||
srnd = self.uparam.get("rand", self.headers.get("rand", ""))
|
srnd = self.uparam.get("rand", self.headers.get("rand", ""))
|
||||||
rnd = int(srnd) if srnd and not self.args.nw else 0
|
rnd = int(srnd) if srnd and not self.args.nw else 0
|
||||||
ac = self.uparam.get(
|
ac = self.uparam.get(
|
||||||
|
@ -1907,7 +1964,7 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
lifetime = 0
|
lifetime = 0
|
||||||
|
|
||||||
return rnd, want_url, lifetime
|
return rnd, want_url, lifetime, vfs.flags.get("xbu"), vfs.flags.get("xau")
|
||||||
|
|
||||||
def handle_plain_upload(self) -> bool:
|
def handle_plain_upload(self) -> bool:
|
||||||
assert self.parser
|
assert self.parser
|
||||||
|
@ -1924,7 +1981,7 @@ class HttpCli(object):
|
||||||
if not nullwrite:
|
if not nullwrite:
|
||||||
bos.makedirs(fdir_base)
|
bos.makedirs(fdir_base)
|
||||||
|
|
||||||
rnd, want_url, lifetime = self.upload_flags(vfs)
|
rnd, want_url, lifetime, xbu, xau = self.upload_flags(vfs)
|
||||||
|
|
||||||
files: list[tuple[int, str, str, str, str, str]] = []
|
files: list[tuple[int, str, str, str, str, str]] = []
|
||||||
# sz, sha_hex, sha_b64, p_file, fname, abspath
|
# sz, sha_hex, sha_b64, p_file, fname, abspath
|
||||||
|
@ -1966,6 +2023,24 @@ class HttpCli(object):
|
||||||
tnam = fname = os.devnull
|
tnam = fname = os.devnull
|
||||||
fdir = abspath = ""
|
fdir = abspath = ""
|
||||||
|
|
||||||
|
if xbu:
|
||||||
|
at = time.time() - lifetime
|
||||||
|
if not runhook(
|
||||||
|
self.log,
|
||||||
|
xbu,
|
||||||
|
abspath,
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.ip,
|
||||||
|
at,
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "upload denied by xbu"
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
if lim:
|
if lim:
|
||||||
lim.chk_bup(self.ip)
|
lim.chk_bup(self.ip)
|
||||||
lim.chk_nup(self.ip)
|
lim.chk_nup(self.ip)
|
||||||
|
@ -2008,6 +2083,24 @@ class HttpCli(object):
|
||||||
files.append(
|
files.append(
|
||||||
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath)
|
(sz, sha_hex, sha_b64, p_file or "(discarded)", fname, abspath)
|
||||||
)
|
)
|
||||||
|
at = time.time() - lifetime
|
||||||
|
if xau and not runhook(
|
||||||
|
self.log,
|
||||||
|
xau,
|
||||||
|
abspath,
|
||||||
|
self.vpath,
|
||||||
|
self.host,
|
||||||
|
self.uname,
|
||||||
|
self.ip,
|
||||||
|
at,
|
||||||
|
sz,
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "upload denied by xau"
|
||||||
|
self.log(t, 1)
|
||||||
|
os.unlink(abspath)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
dbv, vrem = vfs.get_dbv(rem)
|
dbv, vrem = vfs.get_dbv(rem)
|
||||||
self.conn.hsrv.broker.say(
|
self.conn.hsrv.broker.say(
|
||||||
"up2k.hash_file",
|
"up2k.hash_file",
|
||||||
|
@ -2016,7 +2109,7 @@ class HttpCli(object):
|
||||||
vrem,
|
vrem,
|
||||||
fname,
|
fname,
|
||||||
self.ip,
|
self.ip,
|
||||||
time.time() - lifetime,
|
at,
|
||||||
)
|
)
|
||||||
self.conn.nbyte += sz
|
self.conn.nbyte += sz
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ from ipaddress import (
|
||||||
ip_network,
|
ip_network,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .__init__ import TYPE_CHECKING
|
from .__init__ import MACOS, TYPE_CHECKING
|
||||||
from .util import MACOS, Netdev, find_prefix, min_ex, spack
|
from .util import Netdev, find_prefix, min_ex, spack
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
|
|
|
@ -44,6 +44,7 @@ from .util import (
|
||||||
ren_open,
|
ren_open,
|
||||||
rmdirs,
|
rmdirs,
|
||||||
rmdirs_up,
|
rmdirs_up,
|
||||||
|
runhook,
|
||||||
s2hms,
|
s2hms,
|
||||||
s3dec,
|
s3dec,
|
||||||
s3enc,
|
s3enc,
|
||||||
|
@ -2059,6 +2060,8 @@ class Up2k(object):
|
||||||
"sprs": sprs, # dontcare; finished anyways
|
"sprs": sprs, # dontcare; finished anyways
|
||||||
"size": dsize,
|
"size": dsize,
|
||||||
"lmod": dtime,
|
"lmod": dtime,
|
||||||
|
"host": cj["host"],
|
||||||
|
"user": cj["user"],
|
||||||
"addr": ip,
|
"addr": ip,
|
||||||
"at": at,
|
"at": at,
|
||||||
"hash": [],
|
"hash": [],
|
||||||
|
@ -2187,6 +2190,8 @@ class Up2k(object):
|
||||||
}
|
}
|
||||||
# client-provided, sanitized by _get_wark: name, size, lmod
|
# client-provided, sanitized by _get_wark: name, size, lmod
|
||||||
for k in [
|
for k in [
|
||||||
|
"host",
|
||||||
|
"user",
|
||||||
"addr",
|
"addr",
|
||||||
"vtop",
|
"vtop",
|
||||||
"ptop",
|
"ptop",
|
||||||
|
@ -2416,6 +2421,26 @@ class Up2k(object):
|
||||||
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
|
# self.log("--- " + wark + " " + dst + " finish_upload atomic " + dst, 4)
|
||||||
atomic_move(src, dst)
|
atomic_move(src, dst)
|
||||||
|
|
||||||
|
upt = job.get("at") or time.time()
|
||||||
|
xau = self.flags[ptop].get("xau")
|
||||||
|
if xau and not runhook(
|
||||||
|
self.log,
|
||||||
|
xau,
|
||||||
|
dst,
|
||||||
|
djoin(job["vtop"], job["prel"], job["name"]),
|
||||||
|
job["host"],
|
||||||
|
job["user"],
|
||||||
|
job["addr"],
|
||||||
|
upt,
|
||||||
|
job["size"],
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "upload blocked by xau"
|
||||||
|
self.log(t, 1)
|
||||||
|
bos.unlink(dst)
|
||||||
|
self.registry[ptop].pop(wark, None)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
times = (int(time.time()), int(job["lmod"]))
|
times = (int(time.time()), int(job["lmod"]))
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
z1 = (dst, job["size"], times, job["sprs"])
|
z1 = (dst, job["size"], times, job["sprs"])
|
||||||
|
@ -2427,7 +2452,6 @@ class Up2k(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
z2 = [job[x] for x in "ptop wark prel name lmod size addr".split()]
|
z2 = [job[x] for x in "ptop wark prel name lmod size addr".split()]
|
||||||
upt = job.get("at") or time.time()
|
|
||||||
wake_sr = False
|
wake_sr = False
|
||||||
try:
|
try:
|
||||||
flt = job["life"]
|
flt = job["life"]
|
||||||
|
@ -2623,6 +2647,8 @@ class Up2k(object):
|
||||||
self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop))
|
self.log("rm: skip type-{:x} file [{}]".format(st.st_mode, atop))
|
||||||
return 0, [], []
|
return 0, [], []
|
||||||
|
|
||||||
|
xbd = vn.flags.get("xbd")
|
||||||
|
xad = vn.flags.get("xad")
|
||||||
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]:
|
||||||
|
@ -2638,6 +2664,12 @@ class Up2k(object):
|
||||||
vpath = "{}/{}".format(dbv.vpath, volpath).strip("/")
|
vpath = "{}/{}".format(dbv.vpath, volpath).strip("/")
|
||||||
self.log("rm {}\n {}".format(vpath, abspath))
|
self.log("rm {}\n {}".format(vpath, abspath))
|
||||||
_ = dbv.get(volpath, uname, *permsets[0])
|
_ = dbv.get(volpath, uname, *permsets[0])
|
||||||
|
if xbd and not runhook(
|
||||||
|
self.log, xbd, abspath, vpath, "", uname, "", 0, 0, ""
|
||||||
|
):
|
||||||
|
self.log("delete blocked by xbd: {}".format(abspath), 1)
|
||||||
|
continue
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
cur = None
|
cur = None
|
||||||
try:
|
try:
|
||||||
|
@ -2649,6 +2681,8 @@ class Up2k(object):
|
||||||
cur.connection.commit()
|
cur.connection.commit()
|
||||||
|
|
||||||
bos.unlink(abspath)
|
bos.unlink(abspath)
|
||||||
|
if xad:
|
||||||
|
runhook(self.log, xad, abspath, vpath, "", uname, "", 0, 0, "")
|
||||||
|
|
||||||
ok: list[str] = []
|
ok: list[str] = []
|
||||||
ng: list[str] = []
|
ng: list[str] = []
|
||||||
|
@ -2741,6 +2775,13 @@ class Up2k(object):
|
||||||
if bos.path.exists(dabs):
|
if bos.path.exists(dabs):
|
||||||
raise Pebkac(400, "mv2: target file exists")
|
raise Pebkac(400, "mv2: target file exists")
|
||||||
|
|
||||||
|
xbr = svn.flags.get("xbr")
|
||||||
|
xar = dvn.flags.get("xar")
|
||||||
|
if xbr and not runhook(self.log, xbr, sabs, svp, "", uname, "", 0, 0, ""):
|
||||||
|
t = "move blocked by xbr: {}".format(svp)
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(405, t)
|
||||||
|
|
||||||
bos.makedirs(os.path.dirname(dabs))
|
bos.makedirs(os.path.dirname(dabs))
|
||||||
|
|
||||||
if bos.path.islink(sabs):
|
if bos.path.islink(sabs):
|
||||||
|
@ -2757,6 +2798,9 @@ class Up2k(object):
|
||||||
with self.rescan_cond:
|
with self.rescan_cond:
|
||||||
self.rescan_cond.notify_all()
|
self.rescan_cond.notify_all()
|
||||||
|
|
||||||
|
if xar:
|
||||||
|
runhook(self.log, xar, dabs, dvp, "", uname, "", 0, 0, "")
|
||||||
|
|
||||||
return "k"
|
return "k"
|
||||||
|
|
||||||
c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(svn.realpath, srem)
|
c1, w, ftime_, fsize_, ip, at = self._find_from_vpath(svn.realpath, srem)
|
||||||
|
@ -2801,6 +2845,9 @@ class Up2k(object):
|
||||||
|
|
||||||
os.unlink(b1)
|
os.unlink(b1)
|
||||||
|
|
||||||
|
if xar:
|
||||||
|
runhook(self.log, xar, dabs, dvp, "", uname, "", 0, 0, "")
|
||||||
|
|
||||||
return "k"
|
return "k"
|
||||||
|
|
||||||
def _copy_tags(
|
def _copy_tags(
|
||||||
|
@ -3020,6 +3067,25 @@ class Up2k(object):
|
||||||
# if len(job["name"].split(".")) > 8:
|
# if len(job["name"].split(".")) > 8:
|
||||||
# raise Exception("aaa")
|
# raise Exception("aaa")
|
||||||
|
|
||||||
|
xbu = self.flags[job["ptop"]].get("xbu")
|
||||||
|
ap_chk = djoin(pdir, job["name"])
|
||||||
|
vp_chk = djoin(job["vtop"], job["prel"], job["name"])
|
||||||
|
if xbu and not runhook(
|
||||||
|
self.log,
|
||||||
|
xbu,
|
||||||
|
ap_chk,
|
||||||
|
vp_chk,
|
||||||
|
job["host"],
|
||||||
|
job["user"],
|
||||||
|
job["addr"],
|
||||||
|
job["t0"],
|
||||||
|
job["size"],
|
||||||
|
"",
|
||||||
|
):
|
||||||
|
t = "upload blocked by xbu: {}".format(vp_chk)
|
||||||
|
self.log(t, 1)
|
||||||
|
raise Pebkac(403, t)
|
||||||
|
|
||||||
tnam = job["name"] + ".PARTIAL"
|
tnam = job["name"] + ".PARTIAL"
|
||||||
if self.args.dotpart:
|
if self.args.dotpart:
|
||||||
tnam = "." + tnam
|
tnam = "." + tnam
|
||||||
|
|
|
@ -6,6 +6,7 @@ import contextlib
|
||||||
import errno
|
import errno
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
@ -362,8 +363,11 @@ class Daemon(threading.Thread):
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
a: Optional[Iterable[Any]] = None,
|
a: Optional[Iterable[Any]] = None,
|
||||||
r: bool = True,
|
r: bool = True,
|
||||||
|
ka: Optional[dict[Any, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
threading.Thread.__init__(self, target=target, name=name, args=a or ())
|
threading.Thread.__init__(
|
||||||
|
self, target=target, name=name, args=a or (), kwargs=ka
|
||||||
|
)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
if r:
|
if r:
|
||||||
self.start()
|
self.start()
|
||||||
|
@ -2453,6 +2457,124 @@ def retchk(
|
||||||
raise Exception(t)
|
raise Exception(t)
|
||||||
|
|
||||||
|
|
||||||
|
def _runhook(
|
||||||
|
log: "NamedLogger",
|
||||||
|
cmd: str,
|
||||||
|
ap: str,
|
||||||
|
vp: str,
|
||||||
|
host: str,
|
||||||
|
uname: str,
|
||||||
|
ip: str,
|
||||||
|
at: float,
|
||||||
|
sz: int,
|
||||||
|
txt: str,
|
||||||
|
) -> bool:
|
||||||
|
chk = False
|
||||||
|
fork = False
|
||||||
|
jtxt = False
|
||||||
|
wait = 0
|
||||||
|
tout = 0
|
||||||
|
kill = "t"
|
||||||
|
cap = 0
|
||||||
|
ocmd = cmd
|
||||||
|
while "," in cmd[:6]:
|
||||||
|
arg, cmd = cmd.split(",", 1)
|
||||||
|
if arg == "c":
|
||||||
|
chk = True
|
||||||
|
elif arg == "f":
|
||||||
|
fork = True
|
||||||
|
elif arg == "j":
|
||||||
|
jtxt = True
|
||||||
|
elif arg.startswith("w"):
|
||||||
|
wait = float(arg[1:])
|
||||||
|
elif arg.startswith("t"):
|
||||||
|
tout = float(arg[1:])
|
||||||
|
elif arg.startswith("c"):
|
||||||
|
cap = int(arg[1:]) # 0=none 1=stdout 2=stderr 3=both
|
||||||
|
elif arg.startswith("k"):
|
||||||
|
kill = arg[1:] # [t]ree [m]ain [n]one
|
||||||
|
else:
|
||||||
|
t = "hook: invalid flag {} in {}"
|
||||||
|
log(t.format(arg, ocmd))
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
# try:
|
||||||
|
pypath = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
zsl = [str(pypath)] + [str(x) for x in sys.path if x]
|
||||||
|
pypath = str(os.pathsep.join(zsl))
|
||||||
|
env["PYTHONPATH"] = pypath
|
||||||
|
# except: if not E.ox: raise
|
||||||
|
|
||||||
|
ka = {
|
||||||
|
"env": env,
|
||||||
|
"timeout": tout,
|
||||||
|
"kill": kill,
|
||||||
|
"capture": cap,
|
||||||
|
}
|
||||||
|
|
||||||
|
if jtxt:
|
||||||
|
ja = {
|
||||||
|
"ap": ap,
|
||||||
|
"vp": vp,
|
||||||
|
"ip": ip,
|
||||||
|
"host": host,
|
||||||
|
"user": uname,
|
||||||
|
"at": at or time.time(),
|
||||||
|
"sz": sz,
|
||||||
|
"txt": txt,
|
||||||
|
}
|
||||||
|
arg = json.dumps(ja)
|
||||||
|
else:
|
||||||
|
arg = txt or ap
|
||||||
|
|
||||||
|
acmd = [cmd, arg]
|
||||||
|
if cmd.endswith(".py"):
|
||||||
|
acmd = [sys.executable] + acmd
|
||||||
|
|
||||||
|
bcmd = [fsenc(x) for x in acmd]
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
if fork:
|
||||||
|
Daemon(runcmd, ocmd, [acmd], ka=ka)
|
||||||
|
else:
|
||||||
|
rc, v, err = runcmd(bcmd, **ka) # type: ignore
|
||||||
|
if chk and rc:
|
||||||
|
retchk(rc, bcmd, err, log, 5)
|
||||||
|
return False
|
||||||
|
|
||||||
|
wait -= time.time() - t0
|
||||||
|
if wait > 0:
|
||||||
|
time.sleep(wait)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def runhook(
|
||||||
|
log: "NamedLogger",
|
||||||
|
cmds: list[str],
|
||||||
|
ap: str,
|
||||||
|
vp: str,
|
||||||
|
host: str,
|
||||||
|
uname: str,
|
||||||
|
ip: str,
|
||||||
|
at: float,
|
||||||
|
sz: int,
|
||||||
|
txt: str,
|
||||||
|
) -> bool:
|
||||||
|
vp = vp.replace("\\", "/")
|
||||||
|
for cmd in cmds:
|
||||||
|
try:
|
||||||
|
if not _runhook(log, cmd, ap, vp, host, uname, ip, at, sz, txt):
|
||||||
|
return False
|
||||||
|
except Exception as ex:
|
||||||
|
log("hook: {}".format(ex))
|
||||||
|
if ",c," in "," + cmd:
|
||||||
|
return False
|
||||||
|
break
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def gzip_orig_sz(fn: str) -> int:
|
def gzip_orig_sz(fn: str) -> int:
|
||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
f.seek(-4, 2)
|
f.seek(-4, 2)
|
||||||
|
|
|
@ -2322,9 +2322,10 @@ function up2k_init(subtle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err_pend = rsp.indexOf('partial upload exists at a different') + 1,
|
var err_pend = rsp.indexOf('partial upload exists at a different') + 1,
|
||||||
|
err_plug = rsp.indexOf('upload blocked by x') + 1,
|
||||||
err_dupe = rsp.indexOf('upload rejected, file already exists') + 1;
|
err_dupe = rsp.indexOf('upload rejected, file already exists') + 1;
|
||||||
|
|
||||||
if (err_pend || err_dupe) {
|
if (err_pend || err_plug || err_dupe) {
|
||||||
err = rsp;
|
err = rsp;
|
||||||
ofs = err.indexOf('\n/');
|
ofs = err.indexOf('\n/');
|
||||||
if (ofs !== -1) {
|
if (ofs !== -1) {
|
||||||
|
@ -2431,6 +2432,14 @@ function up2k_init(subtle) {
|
||||||
|
|
||||||
function orz(xhr) {
|
function orz(xhr) {
|
||||||
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
||||||
|
if (txt.indexOf('upload blocked by x') + 1) {
|
||||||
|
apop(st.busy.upload, upt);
|
||||||
|
apop(t.postlist, npart);
|
||||||
|
pvis.seth(t.n, 1, "ERROR");
|
||||||
|
pvis.seth(t.n, 2, txt.split(/\n/)[0]);
|
||||||
|
pvis.move(t.n, 'ng');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
pvis.prog(t, npart, cdr - car);
|
pvis.prog(t, npart, cdr - car);
|
||||||
st.bytes.finished += cdr - car;
|
st.bytes.finished += cdr - car;
|
||||||
|
|
Loading…
Reference in a new issue