diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index f309d4fa..ae443063 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -8,7 +8,6 @@ import time import json from datetime import datetime import calendar -import mimetypes from .__init__ import E, PY2, WINDOWS from .util import * # noqa # pylint: disable=unused-wildcard-import @@ -526,7 +525,7 @@ class HttpCli(object): except Pebkac as ex: errmsg = str(ex) - td = time.time() - t0 + td = max(0.1, time.time() - t0) sz_total = sum(x[0] for x in files) spd = (sz_total / td) / (1024 * 1024) @@ -665,6 +664,7 @@ class HttpCli(object): return file_lastmod, int(file_ts) > int(cli_ts) except: self.log("bad lastmod format: {}".format(cli_lastmod)) + self.log(" expected format: {}".format(file_lastmod)) return file_lastmod, file_lastmod != cli_lastmod return file_lastmod, True @@ -789,7 +789,7 @@ class HttpCli(object): self.send_headers( length=upper - lower, status=status, - mime=mimetypes.guess_type(req_path)[0] or "application/octet-stream", + mime=guess_mime(req_path)[0] or "application/octet-stream", ) logmsg += str(status) + logtail diff --git a/copyparty/util.py b/copyparty/util.py index ae2e2bd8..c1d2b342 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -8,6 +8,7 @@ import struct import hashlib import platform import threading +import mimetypes import subprocess as sp # nosec from .__init__ import PY2, WINDOWS @@ -474,6 +475,13 @@ def unescape_cookie(orig): return ret +def guess_mime(url): + if url.endswith(".md"): + return ["text/plain; charset=UTF-8"] + + return mimetypes.guess_type(url) + + def runcmd(*argv): p = sp.Popen(argv, stdout=sp.PIPE, stderr=sp.PIPE) stdout, stderr = p.communicate() diff --git a/copyparty/web/mde.js b/copyparty/web/mde.js index 99a2cfe8..51482c2d 100644 --- a/copyparty/web/mde.js +++ b/copyparty/web/mde.js @@ -47,6 +47,9 @@ var dom_md = document.getElementById('mt'); gfm: true } }, + shortcuts: { + "save": "Ctrl-S" + }, insertTexts: ["[](", ")"], tabSize: 4, toolbar: tbar, @@ -85,10 +88,12 @@ function save(mde) { return; } + var txt = mde.value(); + var fd = new FormData(); fd.append("act", "tput"); fd.append("lastmod", (force ? -1 : last_modified)); - fd.append("body", mde.value()); + fd.append("body", txt); var url = (document.location + '').split('?')[0] + '?raw'; var xhr = new XMLHttpRequest(); @@ -97,6 +102,7 @@ function save(mde) { xhr.onreadystatechange = save_cb; xhr.btn = save_btn; xhr.mde = mde; + xhr.txt = txt; xhr.send(fd); } @@ -138,8 +144,55 @@ function save_cb() { return; } - last_modified = r.lastmod; this.btn.classList.remove('force-save'); - alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512); - md_changed(this.mde, true); + //alert('save OK -- wrote ' + r.size + ' bytes.\n\nsha512: ' + r.sha512); + + // download the saved doc from the server and compare + var url = (document.location + '').split('?')[0] + '?raw'; + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = 'text'; + xhr.onreadystatechange = save_chk; + xhr.btn = this.save_btn; + xhr.mde = this.mde; + xhr.txt = this.txt; + xhr.lastmod = r.lastmod; + xhr.send(); +} + +function save_chk() { + if (this.readyState != XMLHttpRequest.DONE) + return; + + if (this.status !== 200) { + alert('Error! The file was NOT saved.\n\n' + this.status + ": " + (this.responseText + '').replace(/^
/, ""));
+        return;
+    }
+
+    var doc1 = this.txt.replace(/\r/g, "");
+    var doc2 = this.responseText.replace(/\r/g, "");
+    if (doc1 != doc2) {
+        alert(
+            'Error! The document on the server does not appear to have saved correctly (your editor contents and the server copy is not identical). Place the document on your clipboard for now and check the server logs for hints\n\n' +
+            'Length: yours=' + doc1.length + ', server=' + doc2.length
+        );
+        alert('yours, ' + doc1.length + ' byte:\n[' + doc1 + ']');
+        alert('server, ' + doc2.length + ' byte:\n[' + doc2 + ']');
+        return;
+    }
+
+    last_modified = this.lastmod;
+    md_changed(this.mde, true);
+
+    var ok = document.createElement('div');
+    ok.setAttribute('style', 'font-size:6em;font-family:serif;font-weight:bold;color:#cf6;background:#444;border-radius:.3em;padding:.6em 0;position:fixed;top:30%;left:calc(50% - 2em);width:4em;text-align:center;z-index:9001;transition:opacity 0.2s ease-in-out;opacity:1');
+    ok.innerHTML = 'OK✔️';
+    var parent = document.getElementById('m');
+    document.documentElement.appendChild(ok);
+    setTimeout(function () {
+        ok.style.opacity = 0;
+    }, 500);
+    setTimeout(function () {
+        ok.parentNode.removeChild(ok);
+    }, 750);
 }
diff --git a/copyparty/web/upload.html b/copyparty/web/upload.html
index 5198afa9..7d571a18 100644
--- a/copyparty/web/upload.html
+++ b/copyparty/web/upload.html
@@ -22,7 +22,7 @@
         
     
 
-    
+