mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
support rclone as fuse client
This commit is contained in:
parent
90a5cb5e59
commit
b967a92f69
|
@ -128,7 +128,7 @@ def main():
|
||||||
)
|
)
|
||||||
ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind")
|
ap.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind")
|
||||||
ap.add_argument("-p", metavar="PORT", type=int, default=3923, help="port to bind")
|
ap.add_argument("-p", metavar="PORT", type=int, default=3923, help="port to bind")
|
||||||
ap.add_argument("-nc", metavar="NUM", type=int, default=16, help="max num clients")
|
ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"-j", metavar="CORES", type=int, default=1, help="max num cpu cores"
|
"-j", metavar="CORES", type=int, default=1, help="max num cpu cores"
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,9 +16,6 @@ from .util import * # noqa # pylint: disable=unused-wildcard-import
|
||||||
|
|
||||||
if not PY2:
|
if not PY2:
|
||||||
unicode = str
|
unicode = str
|
||||||
from html import escape as html_escape
|
|
||||||
else:
|
|
||||||
from cgi import escape as html_escape # pylint: disable=no-name-in-module
|
|
||||||
|
|
||||||
|
|
||||||
class HttpCli(object):
|
class HttpCli(object):
|
||||||
|
@ -125,6 +122,11 @@ class HttpCli(object):
|
||||||
self.uparam = uparam
|
self.uparam = uparam
|
||||||
self.vpath = unquotep(vpath)
|
self.vpath = unquotep(vpath)
|
||||||
|
|
||||||
|
ua = self.headers.get("user-agent", "")
|
||||||
|
if ua.startswith("rclone/"):
|
||||||
|
uparam["raw"] = True
|
||||||
|
uparam["dots"] = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.mode in ["GET", "HEAD"]:
|
if self.mode in ["GET", "HEAD"]:
|
||||||
return self.handle_get() and self.keepalive
|
return self.handle_get() and self.keepalive
|
||||||
|
@ -141,7 +143,7 @@ class HttpCli(object):
|
||||||
try:
|
try:
|
||||||
# self.log("pebkac at httpcli.run #2: " + repr(ex))
|
# self.log("pebkac at httpcli.run #2: " + repr(ex))
|
||||||
self.keepalive = self._check_nonfatal(ex)
|
self.keepalive = self._check_nonfatal(ex)
|
||||||
self.loud_reply(str(ex), status=ex.code)
|
self.loud_reply("{}: {}".format(str(ex), self.vpath), status=ex.code)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
return False
|
return False
|
||||||
|
@ -180,7 +182,8 @@ class HttpCli(object):
|
||||||
self.send_headers(len(body), status, mime, headers)
|
self.send_headers(len(body), status, mime, headers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.s.sendall(body)
|
if self.mode != "HEAD":
|
||||||
|
self.s.sendall(body)
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "client d/c while replying body")
|
raise Pebkac(400, "client d/c while replying body")
|
||||||
|
|
||||||
|
@ -188,7 +191,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
def loud_reply(self, body, *args, **kwargs):
|
def loud_reply(self, body, *args, **kwargs):
|
||||||
self.log(body.rstrip())
|
self.log(body.rstrip())
|
||||||
self.reply(b"<pre>" + body.encode("utf-8"), *list(args), **kwargs)
|
self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
|
||||||
|
|
||||||
def handle_get(self):
|
def handle_get(self):
|
||||||
logmsg = "{:4} {}".format(self.mode, self.req)
|
logmsg = "{:4} {}".format(self.mode, self.req)
|
||||||
|
@ -493,7 +496,7 @@ class HttpCli(object):
|
||||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||||
html = self.conn.tpl_msg.render(
|
html = self.conn.tpl_msg.render(
|
||||||
h2='<a href="/{}">go to /{}</a>'.format(
|
h2='<a href="/{}">go to /{}</a>'.format(
|
||||||
quotep(vpath), html_escape(vpath, quote=False)
|
quotep(vpath), html_escape(vpath)
|
||||||
),
|
),
|
||||||
pre="aight",
|
pre="aight",
|
||||||
click=True,
|
click=True,
|
||||||
|
@ -527,7 +530,7 @@ class HttpCli(object):
|
||||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||||
html = self.conn.tpl_msg.render(
|
html = self.conn.tpl_msg.render(
|
||||||
h2='<a href="/{}?edit">go to /{}?edit</a>'.format(
|
h2='<a href="/{}?edit">go to /{}?edit</a>'.format(
|
||||||
quotep(vpath), html_escape(vpath, quote=False)
|
quotep(vpath), html_escape(vpath)
|
||||||
),
|
),
|
||||||
pre="aight",
|
pre="aight",
|
||||||
click=True,
|
click=True,
|
||||||
|
@ -621,7 +624,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
html = self.conn.tpl_msg.render(
|
html = self.conn.tpl_msg.render(
|
||||||
h2='<a href="/{}">return to /{}</a>'.format(
|
h2='<a href="/{}">return to /{}</a>'.format(
|
||||||
quotep(self.vpath), html_escape(self.vpath, quote=False)
|
quotep(self.vpath), html_escape(self.vpath)
|
||||||
),
|
),
|
||||||
pre=msg,
|
pre=msg,
|
||||||
)
|
)
|
||||||
|
@ -938,7 +941,7 @@ class HttpCli(object):
|
||||||
|
|
||||||
targs = {
|
targs = {
|
||||||
"edit": "edit" in self.uparam,
|
"edit": "edit" in self.uparam,
|
||||||
"title": html_escape(self.vpath, quote=False),
|
"title": html_escape(self.vpath),
|
||||||
"lastmod": int(ts_md * 1000),
|
"lastmod": int(ts_md * 1000),
|
||||||
"md": "",
|
"md": "",
|
||||||
}
|
}
|
||||||
|
@ -979,7 +982,7 @@ class HttpCli(object):
|
||||||
else:
|
else:
|
||||||
vpath += "/" + node
|
vpath += "/" + node
|
||||||
|
|
||||||
vpnodes.append([quotep(vpath) + "/", html_escape(node, quote=False)])
|
vpnodes.append([quotep(vpath) + "/", html_escape(node)])
|
||||||
|
|
||||||
vn, rem = self.auth.vfs.get(
|
vn, rem = self.auth.vfs.get(
|
||||||
self.vpath, self.uname, self.readable, self.writable
|
self.vpath, self.uname, self.readable, self.writable
|
||||||
|
@ -1054,7 +1057,7 @@ class HttpCli(object):
|
||||||
dt = datetime.utcfromtimestamp(inf.st_mtime)
|
dt = datetime.utcfromtimestamp(inf.st_mtime)
|
||||||
dt = dt.strftime("%Y-%m-%d %H:%M:%S")
|
dt = dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
item = [margin, quotep(href), html_escape(fn, quote=False), sz, dt]
|
item = [margin, quotep(href), html_escape(fn), sz, dt]
|
||||||
if is_dir:
|
if is_dir:
|
||||||
dirs.append(item)
|
dirs.append(item)
|
||||||
else:
|
else:
|
||||||
|
@ -1119,7 +1122,7 @@ class HttpCli(object):
|
||||||
ts=ts,
|
ts=ts,
|
||||||
prologue=logues[0],
|
prologue=logues[0],
|
||||||
epilogue=logues[1],
|
epilogue=logues[1],
|
||||||
title=html_escape(self.vpath, quote=False),
|
title=html_escape(self.vpath),
|
||||||
srv_info="</span> /// <span>".join(srv_info),
|
srv_info="</span> /// <span>".join(srv_info),
|
||||||
)
|
)
|
||||||
self.reply(html.encode("utf-8", "replace"))
|
self.reply(html.encode("utf-8", "replace"))
|
||||||
|
|
|
@ -335,18 +335,18 @@ def read_header(sr):
|
||||||
|
|
||||||
|
|
||||||
def humansize(sz, terse=False):
|
def humansize(sz, terse=False):
|
||||||
for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB']:
|
for unit in ["B", "KiB", "MiB", "GiB", "TiB"]:
|
||||||
if sz < 1024:
|
if sz < 1024:
|
||||||
break
|
break
|
||||||
|
|
||||||
sz /= 1024.
|
sz /= 1024.0
|
||||||
|
|
||||||
ret = ' '.join([str(sz)[:4].rstrip('.'), unit])
|
ret = " ".join([str(sz)[:4].rstrip("."), unit])
|
||||||
|
|
||||||
if not terse:
|
if not terse:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
return ret.replace('iB', '').replace(' ', '')
|
return ret.replace("iB", "").replace(" ", "")
|
||||||
|
|
||||||
|
|
||||||
def undot(path):
|
def undot(path):
|
||||||
|
@ -398,6 +398,21 @@ def exclude_dotfiles(filepaths):
|
||||||
yield fpath
|
yield fpath
|
||||||
|
|
||||||
|
|
||||||
|
def html_escape(s, quote=False):
|
||||||
|
"""html.escape but also newlines"""
|
||||||
|
s = (
|
||||||
|
s.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\r", " ")
|
||||||
|
.replace("\n", " ")
|
||||||
|
)
|
||||||
|
if quote:
|
||||||
|
s = s.replace('"', """).replace("'", "'")
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def quotep(txt):
|
def quotep(txt):
|
||||||
"""url quoter which deals with bytes correctly"""
|
"""url quoter which deals with bytes correctly"""
|
||||||
btxt = w8enc(txt)
|
btxt = w8enc(txt)
|
||||||
|
@ -412,8 +427,8 @@ def quotep(txt):
|
||||||
def unquotep(txt):
|
def unquotep(txt):
|
||||||
"""url unquoter which deals with bytes correctly"""
|
"""url unquoter which deals with bytes correctly"""
|
||||||
btxt = w8enc(txt)
|
btxt = w8enc(txt)
|
||||||
unq1 = btxt.replace(b"+", b" ")
|
# btxt = btxt.replace(b"+", b" ")
|
||||||
unq2 = unquote(unq1)
|
unq2 = unquote(btxt)
|
||||||
return w8dec(unq2)
|
return w8dec(unq2)
|
||||||
|
|
||||||
|
|
||||||
|
|
164
scripts/speedtest-fs.py
Normal file
164
scripts/speedtest-fs.py
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import stat
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import traceback
|
||||||
|
import threading
|
||||||
|
from queue import Queue
|
||||||
|
|
||||||
|
|
||||||
|
"""speedtest-fs: filesystem performance estimate"""
|
||||||
|
__author__ = "ed <copyparty@ocv.me>"
|
||||||
|
__copyright__ = 2020
|
||||||
|
__license__ = "MIT"
|
||||||
|
__url__ = "https://github.com/9001/copyparty/"
|
||||||
|
|
||||||
|
|
||||||
|
def get_spd(nbyte, nsec):
|
||||||
|
if not nsec:
|
||||||
|
return "0.000 MB 0.000 sec 0.000 MB/s"
|
||||||
|
|
||||||
|
mb = nbyte / (1024 * 1024.0)
|
||||||
|
spd = mb / nsec
|
||||||
|
|
||||||
|
return f"{mb:.3f} MB {nsec:.3f} sec {spd:.3f} MB/s"
|
||||||
|
|
||||||
|
|
||||||
|
class Inf(object):
|
||||||
|
def __init__(self, t0):
|
||||||
|
self.msgs = []
|
||||||
|
self.errors = []
|
||||||
|
self.reports = []
|
||||||
|
self.mtx_msgs = threading.Lock()
|
||||||
|
self.mtx_reports = threading.Lock()
|
||||||
|
|
||||||
|
self.n_byte = 0
|
||||||
|
self.n_sec = 0
|
||||||
|
self.n_done = 0
|
||||||
|
self.t0 = t0
|
||||||
|
|
||||||
|
thr = threading.Thread(target=self.print_msgs)
|
||||||
|
thr.daemon = True
|
||||||
|
thr.start()
|
||||||
|
|
||||||
|
def msg(self, fn, n_read):
|
||||||
|
with self.mtx_msgs:
|
||||||
|
self.msgs.append(f"{fn} {n_read}")
|
||||||
|
|
||||||
|
def err(self, fn):
|
||||||
|
with self.mtx_reports:
|
||||||
|
self.errors.append(f"{fn}\n{traceback.format_exc()}")
|
||||||
|
|
||||||
|
def print_msgs(self):
|
||||||
|
while True:
|
||||||
|
time.sleep(0.02)
|
||||||
|
with self.mtx_msgs:
|
||||||
|
msgs = self.msgs
|
||||||
|
self.msgs = []
|
||||||
|
|
||||||
|
if not msgs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
msgs = msgs[-64:]
|
||||||
|
msgs = [f"{get_spd(self.n_byte, self.n_sec)} {x}" for x in msgs]
|
||||||
|
print("\n".join(msgs))
|
||||||
|
|
||||||
|
def report(self, fn, n_byte, n_sec):
|
||||||
|
with self.mtx_reports:
|
||||||
|
self.reports.append([n_byte, n_sec, fn])
|
||||||
|
self.n_byte += n_byte
|
||||||
|
self.n_sec += n_sec
|
||||||
|
|
||||||
|
def done(self):
|
||||||
|
with self.mtx_reports:
|
||||||
|
self.n_done += 1
|
||||||
|
|
||||||
|
|
||||||
|
def get_files(dir_path):
|
||||||
|
for fn in os.listdir(dir_path):
|
||||||
|
fn = os.path.join(dir_path, fn)
|
||||||
|
st = os.stat(fn).st_mode
|
||||||
|
|
||||||
|
if stat.S_ISDIR(st):
|
||||||
|
yield from get_files(fn)
|
||||||
|
|
||||||
|
if stat.S_ISREG(st):
|
||||||
|
yield fn
|
||||||
|
|
||||||
|
|
||||||
|
def worker(q, inf, read_sz):
|
||||||
|
while True:
|
||||||
|
fn = q.get()
|
||||||
|
if not fn:
|
||||||
|
break
|
||||||
|
|
||||||
|
n_read = 0
|
||||||
|
try:
|
||||||
|
t0 = time.time()
|
||||||
|
with open(fn, "rb") as f:
|
||||||
|
while True:
|
||||||
|
buf = f.read(read_sz)
|
||||||
|
if not buf:
|
||||||
|
break
|
||||||
|
|
||||||
|
n_read += len(buf)
|
||||||
|
inf.msg(fn, n_read)
|
||||||
|
|
||||||
|
inf.report(fn, n_read, time.time() - t0)
|
||||||
|
except:
|
||||||
|
inf.err(fn)
|
||||||
|
|
||||||
|
inf.done()
|
||||||
|
|
||||||
|
|
||||||
|
def sighandler(signo, frame):
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
signal.signal(signal.SIGINT, sighandler)
|
||||||
|
|
||||||
|
root = "."
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
root = sys.argv[1]
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
q = Queue(256)
|
||||||
|
inf = Inf(t0)
|
||||||
|
|
||||||
|
num_threads = 8
|
||||||
|
read_sz = 32 * 1024
|
||||||
|
for _ in range(num_threads):
|
||||||
|
thr = threading.Thread(target=worker, args=(q, inf, read_sz,))
|
||||||
|
thr.daemon = True
|
||||||
|
thr.start()
|
||||||
|
|
||||||
|
for fn in get_files(root):
|
||||||
|
q.put(fn)
|
||||||
|
|
||||||
|
for _ in range(num_threads):
|
||||||
|
q.put(None)
|
||||||
|
|
||||||
|
while inf.n_done < num_threads:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
t2 = time.time()
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
log = inf.reports
|
||||||
|
log.sort()
|
||||||
|
for nbyte, nsec, fn in log[-64:]:
|
||||||
|
print(f"{get_spd(nbyte, nsec)} {fn}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("\n".join(inf.errors))
|
||||||
|
|
||||||
|
print(get_spd(inf.n_byte, t2 - t0))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
Loading…
Reference in a new issue