Compare commits

...

5 Commits

Author SHA1 Message Date
ed
09557fbe83 v0.4.2 2020-05-15 01:02:18 +02:00
ed
1c0f44fa4e more 206 correctness 2020-05-15 00:52:57 +02:00
ed
fc4d59d2d7 improve autoindent 2020-05-15 00:39:36 +02:00
ed
12345fbacc fix editor cursor (especially in firefox) 2020-05-15 00:03:26 +02:00
ed
2e33c8d222 improve http206 and fuse-client 2020-05-15 00:00:49 +02:00
10 changed files with 168 additions and 77 deletions

48
bin/copyparty-fuse.py Executable file → Normal file
View File

@@ -22,7 +22,9 @@ from urllib.parse import quote_from_bytes as quote
try: try:
from fuse import FUSE, FuseOSError, Operations from fuse import FUSE, FuseOSError, Operations
except: except:
print("\n could not import fuse;\n pip install fusepy\n") print(
"\n could not import fuse; these may help:\n python3 -m pip install --user fusepy\n apt install libfuse\n modprobe fuse"
)
raise raise
@@ -34,9 +36,7 @@ usage:
dependencies: dependencies:
sudo apk add fuse-dev sudo apk add fuse-dev
python3 -m venv ~/pe/ve.fusepy python3 -m pip install --user fusepy
. ~/pe/ve.fusepy/bin/activate
pip install fusepy
MB/s MB/s
@@ -60,20 +60,21 @@ def boring_log(msg):
def rice_tid(): def rice_tid():
tid = threading.current_thread().ident tid = threading.current_thread().ident
c = struct.unpack(b"B" * 5, struct.pack(b">Q", tid)[-5:]) c = struct.unpack(b"B" * 5, struct.pack(b">Q", tid)[-5:])
return "".join("\033[1;37;48;5;{}m{:02x}".format(x, x) for x in c) return "".join("\033[1;37;48;5;{}m{:02x}".format(x, x) for x in c) + "\033[0m"
def fancy_log(msg): def fancy_log(msg):
print("{}\033[0m {}\n".format(rice_tid(), msg), end="") print("{} {}\n".format(rice_tid(), msg), end="")
def null_log(msg): def null_log(msg):
pass pass
log = boring_log info = fancy_log
log = fancy_log log = fancy_log
log = threadless_log dbg = fancy_log
log = null_log
dbg = null_log dbg = null_log
@@ -118,7 +119,7 @@ class Gateway(object):
try: try:
return self.conns[tid] return self.conns[tid]
except: except:
log("new conn [{}] [{}]".format(self.web_host, self.web_port)) info("new conn [{}] [{}]".format(self.web_host, self.web_port))
conn = http.client.HTTPConnection(self.web_host, self.web_port, timeout=260) conn = http.client.HTTPConnection(self.web_host, self.web_port, timeout=260)
@@ -152,7 +153,7 @@ class Gateway(object):
if r.status != 200: if r.status != 200:
self.closeconn() self.closeconn()
raise Exception( raise Exception(
"http error {} reading dir {} in {:x}".format( "http error {} reading dir {} in {}".format(
r.status, web_path, rice_tid() r.status, web_path, rice_tid()
) )
) )
@@ -161,14 +162,14 @@ class Gateway(object):
def download_file_range(self, path, ofs1, ofs2): def download_file_range(self, path, ofs1, ofs2):
web_path = "/" + "/".join([self.web_root, path]) web_path = "/" + "/".join([self.web_root, path])
hdr_range = "bytes={}-{}".format(ofs1, ofs2) hdr_range = "bytes={}-{}".format(ofs1, ofs2 - 1)
log("downloading {}".format(hdr_range)) log("downloading {}".format(hdr_range))
r = self.sendreq("GET", self.quotep(web_path), headers={"Range": hdr_range}) r = self.sendreq("GET", self.quotep(web_path), headers={"Range": hdr_range})
if r.status != http.client.PARTIAL_CONTENT: if r.status != http.client.PARTIAL_CONTENT:
self.closeconn() self.closeconn()
raise Exception( raise Exception(
"http error {} reading file {} range {} in {:x}".format( "http error {} reading file {} range {} in {}".format(
r.status, web_path, hdr_range, rice_tid() r.status, web_path, hdr_range, rice_tid()
) )
) )
@@ -246,14 +247,14 @@ class CPPF(Operations):
self.filecache = [] self.filecache = []
self.filecache_mtx = threading.Lock() self.filecache_mtx = threading.Lock()
log("up") info("up")
def clean_dircache(self): def clean_dircache(self):
"""not threadsafe""" """not threadsafe"""
now = time.time() now = time.time()
cutoff = 0 cutoff = 0
for cn in self.dircache: for cn in self.dircache:
if cn.ts - now > 1: if now - cn.ts > 1:
cutoff += 1 cutoff += 1
else: else:
break break
@@ -398,7 +399,7 @@ class CPPF(Operations):
) )
) )
buf = self.gw.download_file_range(path, h_ofs, h_end - 1) buf = self.gw.download_file_range(path, h_ofs, h_end)
ret = buf[-buf_ofs:] + cdr ret = buf[-buf_ofs:] + cdr
elif car: elif car:
@@ -416,7 +417,7 @@ class CPPF(Operations):
) )
) )
buf = self.gw.download_file_range(path, h_ofs, h_end - 1) buf = self.gw.download_file_range(path, h_ofs, h_end)
ret = car + buf[:buf_ofs] ret = car + buf[:buf_ofs]
else: else:
@@ -438,7 +439,7 @@ class CPPF(Operations):
) )
) )
buf = self.gw.download_file_range(path, h_ofs, h_end - 1) buf = self.gw.download_file_range(path, h_ofs, h_end)
ret = buf[buf_ofs:buf_end] ret = buf[buf_ofs:buf_end]
cn = CacheNode([path, h_ofs], buf) cn = CacheNode([path, h_ofs], buf)
@@ -472,13 +473,16 @@ class CPPF(Operations):
log("read {} @ {} len {} end {}".format(path, offset, length, ofs2)) log("read {} @ {} len {} end {}".format(path, offset, length, ofs2))
file_sz = self.getattr(path)["st_size"] file_sz = self.getattr(path)["st_size"]
if ofs2 >= file_sz: if ofs2 > file_sz:
ofs2 = file_sz - 1 ofs2 = file_sz
log("truncate to len {} end {}".format((ofs2 - offset) + 1, ofs2)) log("truncate to len {} end {}".format(ofs2 - offset, ofs2))
if file_sz == 0 or offset >= ofs2:
return b""
# toggle cache here i suppose # toggle cache here i suppose
# return self.get_cached_file(path, offset, ofs2, file_sz) # return self.get_cached_file(path, offset, ofs2, file_sz)
return self.gw.download_file_range(path, offset, ofs2 - 1) return self.gw.download_file_range(path, offset, ofs2)
def getattr(self, path, fh=None): def getattr(self, path, fh=None):
path = path.strip("/") path = path.strip("/")
@@ -495,7 +499,7 @@ class CPPF(Operations):
cn = self.get_cached_dir(dirpath) cn = self.get_cached_dir(dirpath)
if cn: if cn:
# log('cache ok') log("cache ok")
dents = cn.data dents = cn.data
else: else:
log("cache miss") log("cache miss")

View File

@@ -1,8 +1,8 @@
# coding: utf-8 # coding: utf-8
VERSION = (0, 4, 1) VERSION = (0, 4, 2)
CODENAME = "NIH" CODENAME = "NIH"
BUILD_DT = (2020, 5, 14) BUILD_DT = (2020, 5, 15)
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

@@ -769,11 +769,18 @@ class HttpCli(object):
else: else:
upper = file_sz upper = file_sz
if lower < 0 or lower >= file_sz or upper < 0 or upper > file_sz: if upper > file_sz:
upper = file_sz
if lower < 0 or lower >= upper:
raise Exception() raise Exception()
except: except:
raise Pebkac(400, "invalid range requested: " + hrange) err = "invalid range ({}), size={}".format(hrange, file_sz)
self.loud_reply(err, status=416, headers={
"Content-Range": "bytes */{}".format(file_sz)
})
return True
status = 206 status = 206
self.out_headers["Content-Range"] = "bytes {}-{}/{}".format( self.out_headers["Content-Range"] = "bytes {}-{}/{}".format(

View File

@@ -80,8 +80,9 @@ class HttpSrv(object):
"%s %s" % addr, "%s %s" % addr,
"shut_rdwr err:\n {}\n {}".format(repr(sck), ex), "shut_rdwr err:\n {}\n {}".format(repr(sck), ex),
) )
if ex.errno not in [10038, 107, 57, 9]: if ex.errno not in [10038, 10054, 107, 57, 9]:
# 10038 No longer considered a socket # 10038 No longer considered a socket
# 10054 Foribly closed by remote
# 107 Transport endpoint not connected # 107 Transport endpoint not connected
# 57 Socket is not connected # 57 Socket is not connected
# 9 Bad file descriptor # 9 Bad file descriptor

View File

@@ -49,6 +49,7 @@ HTTPCODE = {
404: "Not Found", 404: "Not Found",
405: "Method Not Allowed", 405: "Method Not Allowed",
413: "Payload Too Large", 413: "Payload Too Large",
416: "Requested Range Not Satisfiable",
422: "Unprocessable Entity", 422: "Unprocessable Entity",
500: "Internal Server Error", 500: "Internal Server Error",
501: "Not Implemented", 501: "Not Implemented",
@@ -309,18 +310,7 @@ def get_boundary(headers):
def read_header(sr): def read_header(sr):
ret = b"" ret = b""
while True: while True:
if ret.endswith(b"\r\n\r\n"): buf = sr.recv(1024)
break
elif ret.endswith(b"\r\n\r"):
n = 1
elif ret.endswith(b"\r\n"):
n = 2
elif ret.endswith(b"\r"):
n = 3
else:
n = 4
buf = sr.recv(n)
if not buf: if not buf:
if not ret: if not ret:
return None return None
@@ -332,11 +322,15 @@ def read_header(sr):
) )
ret += buf ret += buf
ofs = ret.find(b"\r\n\r\n")
if ofs < 0:
if len(ret) > 1024 * 64:
raise Pebkac(400, "header 2big")
else:
continue
if len(ret) > 1024 * 64: sr.unrecv(ret[ofs + 4 :])
raise Pebkac(400, "header 2big") return ret[:ofs].decode("utf-8", "surrogateescape").split("\r\n")
return ret[:-4].decode("utf-8", "surrogateescape").split("\r\n")
def undot(path): def undot(path):

View File

@@ -30,6 +30,11 @@ function cls(dom, name, add) {
} }
function static(obj) {
return JSON.parse(JSON.stringify(obj));
}
// add navbar // add navbar
(function () { (function () {
var n = document.location + ''; var n = document.location + '';

View File

@@ -323,13 +323,26 @@ function save_chk() {
} }
// firefox bug: initial selection offset isn't cleared properly through js
var ff_clearsel = (function () {
if (navigator.userAgent.indexOf(') Gecko/') === -1)
return function () { }
return function () {
var txt = dom_src.value;
var y = dom_src.scrollTop;
dom_src.value = '';
dom_src.value = txt;
dom_src.scrollTop = y;
};
})();
// returns car/cdr (selection bounds) and n1/n2 (grown to full lines) // returns car/cdr (selection bounds) and n1/n2 (grown to full lines)
function linebounds(just_car) { function linebounds(just_car, greedy_growth) {
var car = dom_src.selectionStart, var car = dom_src.selectionStart,
cdr = dom_src.selectionEnd; cdr = dom_src.selectionEnd;
dbg(car, cdr);
if (just_car) if (just_car)
cdr = car; cdr = car;
@@ -337,11 +350,13 @@ function linebounds(just_car) {
n1 = Math.max(car, 0), n1 = Math.max(car, 0),
n2 = Math.min(cdr, md.length - 1); n2 = Math.min(cdr, md.length - 1);
if (n1 < n2 && md[n1] == '\n') if (greedy_growth !== true) {
n1++; if (n1 < n2 && md[n1] == '\n')
n1++;
if (n1 < n2 && md[n2 - 1] == '\n') if (n1 < n2 && md[n2 - 1] == '\n')
n2 -= 2; n2 -= 2;
}
n1 = md.lastIndexOf('\n', n1 - 1) + 1; n1 = md.lastIndexOf('\n', n1 - 1) + 1;
n2 = md.indexOf('\n', n2); n2 = md.indexOf('\n', n2);
@@ -375,7 +390,7 @@ function setsel(s) {
s.cdr = s.pre.length + s.sel.length; s.cdr = s.pre.length + s.sel.length;
} }
dom_src.value = [s.pre, s.sel, s.post].join(''); dom_src.value = [s.pre, s.sel, s.post].join('');
dom_src.setSelectionRange(s.car, s.cdr); dom_src.setSelectionRange(s.car, s.cdr, dom_src.selectionDirection);
dom_src.oninput(); dom_src.oninput();
} }
@@ -416,17 +431,32 @@ function md_header(dedent) {
// smart-home // smart-home
function md_home(shift) { function md_home(shift) {
var s = linebounds(!shift), var s = linebounds(false, true),
ln = s.md.substring(s.n1, s.n2), ln = s.md.substring(s.n1, s.n2),
m = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/.exec(ln), dir = dom_src.selectionDirection,
home = s.n1 + m[0].length, rev = dir === 'backward',
car = (s.car == home) ? s.n1 : home, p1 = rev ? s.car : s.cdr,
cdr = shift ? s.cdr : car; p2 = rev ? s.cdr : s.car,
home = 0,
lf = ln.lastIndexOf('\n') + 1,
re = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/;
if (car > cdr) if (rev)
car = [cdr, cdr = car][0]; home = s.n1 + re.exec(ln)[0].length;
else
home = s.n1 + lf + re.exec(ln.substring(lf))[0].length;
dom_src.setSelectionRange(car, cdr); p1 = (p1 !== home) ? home : (rev ? s.n1 : s.n1 + lf);
if (!shift)
p2 = p1;
if (rev !== p1 < p2)
dir = rev ? 'forward' : 'backward';
if (!shift)
ff_clearsel();
dom_src.setSelectionRange(Math.min(p1, p2), Math.max(p1, p2), dir);
} }
@@ -434,9 +464,14 @@ function md_home(shift) {
function md_newline() { function md_newline() {
var s = linebounds(true), var s = linebounds(true),
ln = s.md.substring(s.n1, s.n2), ln = s.md.substring(s.n1, s.n2),
m = /^[ \t>+-]*(\* )?([0-9]+\. +)?/.exec(ln); m1 = /^( *)([0-9]+)(\. +)/.exec(ln),
m2 = /^[ \t>+-]*(\* )?/.exec(ln);
s.pre = s.md.substring(0, s.car) + '\n' + m[0]; var pre = m2[0];
if (m1 !== null)
pre = m1[1] + (parseInt(m1[2]) + 1) + m1[3];
s.pre = s.md.substring(0, s.car) + '\n' + pre;
s.sel = ''; s.sel = '';
s.post = s.md.substring(s.car); s.post = s.md.substring(s.car);
s.car = s.cdr = s.pre.length; s.car = s.cdr = s.pre.length;
@@ -444,6 +479,25 @@ function md_newline() {
} }
// backspace
function md_backspace() {
var s = linebounds(true),
ln = s.md.substring(s.n1, s.n2),
m = /^[ \t>+-]*(\* )?([0-9]+\. +)?/.exec(ln);
var v = m[0].replace(/[^ ]/g, " ");
if (v === m[0] || v.length !== ln.length)
return true;
s.pre = s.md.substring(0, s.n1) + v;
s.sel = '';
s.post = s.md.substring(s.car);
s.car = s.cdr = s.pre.length;
setsel(s);
return false;
}
// hotkeys / toolbar // hotkeys / toolbar
(function () { (function () {
function keydown(ev) { function keydown(ev) {
@@ -484,6 +538,9 @@ function md_newline() {
action_stack.redo(); action_stack.redo();
return false; return false;
} }
if (!ctrl && !ev.shiftKey && kc == 8) {
return md_backspace();
}
} }
} }
document.onkeydown = keydown; document.onkeydown = keydown;
@@ -507,8 +564,10 @@ document.getElementById('help').onclick = function (e) {
// blame steen // blame steen
action_stack = (function () { action_stack = (function () {
var undos = []; var hist = {
var redos = []; un: [],
re: []
};
var sched_cpos = 0; var sched_cpos = 0;
var sched_timer = null; var sched_timer = null;
var ignore = false; var ignore = false;
@@ -553,7 +612,7 @@ action_stack = (function () {
} }
function apply(src, dst) { function apply(src, dst) {
dbg('undos(%d) redos(%d)', undos.length, redos.length); dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
if (src.length === 0) if (src.length === 0)
return false; return false;
@@ -581,36 +640,36 @@ action_stack = (function () {
ignore = false; ignore = false;
return; return;
} }
redos = []; hist.re = [];
clearTimeout(sched_timer); clearTimeout(sched_timer);
sched_cpos = dom_src.selectionEnd; sched_cpos = dom_src.selectionEnd;
sched_timer = setTimeout(push, 500); sched_timer = setTimeout(push, 500);
} }
function undo() { function undo() {
if (redos.length == 0) { if (hist.re.length == 0) {
clearTimeout(sched_timer); clearTimeout(sched_timer);
push(); push();
} }
return apply(undos, redos); return apply(hist.un, hist.re);
} }
function redo() { function redo() {
return apply(redos, undos); return apply(hist.re, hist.un);
} }
function push() { function push() {
var newtxt = dom_src.value; var newtxt = dom_src.value;
var change = diff(ref, newtxt, sched_cpos); var change = diff(ref, newtxt, sched_cpos);
if (change !== null) if (change !== null)
undos.push(change); hist.un.push(change);
ref = newtxt; ref = newtxt;
dbg('undos(%d) redos(%d)', undos.length, redos.length); dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
if (undos.length > 0) if (hist.un.length > 0)
dbg(undos.slice(-1)[0]); dbg(static(hist.un.slice(-1)[0]));
if (redos.length > 0) if (hist.re.length > 0)
dbg(redos.slice(-1)[0]); dbg(static(hist.re.slice(-1)[0]));
} }
return { return {
@@ -618,8 +677,7 @@ action_stack = (function () {
undo: undo, undo: undo,
redo: redo, redo: redo,
push: schedule_push, push: schedule_push,
_undos: undos, _hist: hist,
_redos: redos,
_ref: ref _ref: ref
} }
})(); })();

View File

@@ -80,3 +80,22 @@ for d in /usr /var; do find $d -type f -size +30M 2>/dev/null; done | while IFS=
# py2 on osx # py2 on osx
brew install python@2 brew install python@2
pip install virtualenv pip install virtualenv
##
## http 206
# az = abcdefghijklmnopqrstuvwxyz
printf '%s\r\n' 'GET /az HTTP/1.1' 'Host: ocv.me' 'Range: bytes=5-10' '' | ncat ocv.me 80
# Content-Range: bytes 5-10/26
# Content-Length: 6
# fghijk
Range: bytes=0-1 "ab" Content-Range: bytes 0-1/26
Range: bytes=24-24 "y" Content-Range: bytes 24-24/26
Range: bytes=24-25 "yz" Content-Range: bytes 24-25/26
Range: bytes=24- "yz" Content-Range: bytes 24-25/26
Range: bytes=25-29 "z" Content-Range: bytes 25-25/26
Range: bytes=26- Content-Range: bytes */26
HTTP/1.1 416 Requested Range Not Satisfiable

View File

@@ -180,7 +180,7 @@ diff --git a/src/Parser.js b/src/Parser.js
+ // similar to tables, writing contents before the <ul> tag + // similar to tables, writing contents before the <ul> tag
+ // so update the tag attribute as we go + // so update the tag attribute as we go
+ // (assuming all list entries got tagged with a source-line, probably safe w) + // (assuming all list entries got tagged with a source-line, probably safe w)
+ body += this.renderer.tag_ln(item.tokens[0].ln).listitem(itemBody, task, checked); + body += this.renderer.tag_ln((item.tokens[0] || token).ln).listitem(itemBody, task, checked);
} }
- out += this.renderer.list(body, ordered, start); - out += this.renderer.list(body, ordered, start);

View File

@@ -166,3 +166,6 @@ chmod 755 $sfx_out.*
printf "done:\n" printf "done:\n"
printf " %s\n" "$(realpath $sfx_out)."{sh,py} printf " %s\n" "$(realpath $sfx_out)."{sh,py}
# rm -rf * # rm -rf *
# tar -tvf ../sfx/tar | sed -r 's/(.* ....-..-.. ..:.. )(.*)/\2 `` \1/' | sort | sed -r 's/(.*) `` (.*)/\2 \1/'| less
# for n in {1..9}; do tar -tf tar | grep -vE '/$' | sed -r 's/(.*)\.(.*)/\2.\1/' | sort | sed -r 's/([^\.]+)\.(.*)/\2.\1/' | tar -cT- | bzip2 -c$n | wc -c; done