Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abcdf479e6 | ||
|
|
ad2371f810 | ||
|
|
c4e2b0f95f | ||
|
|
3da62ec234 | ||
|
|
01233991f3 | ||
|
|
ee35974273 | ||
|
|
7037e7365e | ||
|
|
03b13e8a1c | ||
|
|
cdd2da0208 |
@@ -1,4 +1,6 @@
|
||||
# 💾🎉 copyparty
|
||||
<img src="docs/logo.svg" width="250" align="right"/>
|
||||
|
||||
### 💾🎉 copyparty
|
||||
|
||||
turn almost any device into a file server with resumable uploads/downloads using [*any*](#browser-support) web browser
|
||||
|
||||
@@ -777,6 +779,8 @@ specify `--shr /foobar` to enable this feature; a toplevel virtual folder named
|
||||
|
||||
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
|
||||
|
||||
after a share has expired, it remains visible in the controlpanel for `--shr-rt` minutes (default is 1 day), and the owner can revive it by extending the expiration time there
|
||||
|
||||
**security note:** using this feature does not mean that you can skip the [accounts and volumes](#accounts-and-volumes) section -- you still need to restrict access to volumes that you do not intend to share with unauthenticated users! it is not sufficient to use rules in the reverseproxy to restrict access to just the `/share` folder.
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Maintainer: icxes <dev.null@need.moe>
|
||||
pkgname=copyparty
|
||||
pkgver="1.14.1"
|
||||
pkgver="1.14.2"
|
||||
pkgrel=1
|
||||
pkgdesc="File server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++"
|
||||
arch=("any")
|
||||
@@ -21,7 +21,7 @@ optdepends=("ffmpeg: thumbnails for videos, images (slower) and audio, music tag
|
||||
)
|
||||
source=("https://github.com/9001/${pkgname}/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
backup=("etc/${pkgname}.d/init" )
|
||||
sha256sums=("fa813298122b643a1d131ba71e3c2895be8098ddd887175b166d0857454e809f")
|
||||
sha256sums=("a39f3950c663671d635c453d1a400f6cec6ec827e7dc9d22c3e791b8ab54017b")
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/${pkgname}-${pkgver}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.14.1/copyparty-sfx.py",
|
||||
"version": "1.14.1",
|
||||
"hash": "sha256-TVwi07PPpe2+CCH+f4EoXr/c6aI+SsGWEVLUPm0YRfk="
|
||||
"url": "https://github.com/9001/copyparty/releases/download/v1.14.2/copyparty-sfx.py",
|
||||
"version": "1.14.2",
|
||||
"hash": "sha256-n9Dj2MMrvkWhlXAKWOXn5YQsFCxNpgo5HDFQ111a66A="
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
(function() {
|
||||
|
||||
// usage: copy this to '.banner.js' in your webroot,
|
||||
// and run copyparty with the following argument:
|
||||
// --body-foot '<script src="/.banner.js"></script>'
|
||||
// and run copyparty with the following arguments:
|
||||
// --js-browser /.banner.js --js-other /.banner.js
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -975,9 +975,10 @@ def add_fs(ap):
|
||||
def add_share(ap):
|
||||
db_path = os.path.join(E.cfg, "shares.db")
|
||||
ap2 = ap.add_argument_group('share-url options')
|
||||
ap2.add_argument("--shr", metavar="DIR", default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
|
||||
ap2.add_argument("--shr-db", metavar="FILE", default=db_path, help="database to store shares in")
|
||||
ap2.add_argument("--shr-adm", metavar="U,U", default="", help="comma-separated list of users allowed to view/delete any share")
|
||||
ap2.add_argument("--shr", metavar="DIR", type=u, default="", help="toplevel virtual folder for shared files/folders, for example [\033[32m/share\033[0m]")
|
||||
ap2.add_argument("--shr-db", metavar="FILE", type=u, default=db_path, help="database to store shares in")
|
||||
ap2.add_argument("--shr-adm", metavar="U,U", type=u, default="", help="comma-separated list of users allowed to view/delete any share")
|
||||
ap2.add_argument("--shr-rt", metavar="MIN", type=int, default=1440, help="shares can be revived by their owner if they expired less than MIN minutes ago; [\033[32m60\033[0m]=hour, [\033[32m1440\033[0m]=day, [\033[32m10080\033[0m]=week")
|
||||
ap2.add_argument("--shr-v", action="store_true", help="debug")
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (1, 14, 2)
|
||||
VERSION = (1, 14, 3)
|
||||
CODENAME = "one step forward"
|
||||
BUILD_DT = (2024, 8, 23)
|
||||
BUILD_DT = (2024, 8, 30)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -1611,8 +1611,8 @@ class HttpCli(object):
|
||||
if "delete" in self.uparam:
|
||||
return self.handle_rm([])
|
||||
|
||||
if "unshare" in self.uparam:
|
||||
return self.handle_unshare()
|
||||
if "eshare" in self.uparam:
|
||||
return self.handle_eshare()
|
||||
|
||||
if "application/octet-stream" in ctype:
|
||||
return self.handle_post_binary()
|
||||
@@ -4304,7 +4304,7 @@ class HttpCli(object):
|
||||
self.reply(html.encode("utf-8"), status=200)
|
||||
return True
|
||||
|
||||
def handle_unshare(self) -> bool:
|
||||
def handle_eshare(self) -> bool:
|
||||
idx = self.conn.get_u2idx()
|
||||
if not idx or not hasattr(idx, "p_end"):
|
||||
if not HAVE_SQLITE3:
|
||||
@@ -4312,7 +4312,7 @@ class HttpCli(object):
|
||||
raise Pebkac(500, "server busy, cannot create share; please retry in a bit")
|
||||
|
||||
if self.args.shr_v:
|
||||
self.log("handle_unshare: " + self.req)
|
||||
self.log("handle_eshare: " + self.req)
|
||||
|
||||
cur = idx.get_shr()
|
||||
if not cur:
|
||||
@@ -4320,18 +4320,36 @@ class HttpCli(object):
|
||||
|
||||
skey = self.vpath.split("/")[-1]
|
||||
|
||||
uns = cur.execute("select un from sh where k = ?", (skey,)).fetchall()
|
||||
un = uns[0][0] if uns and uns[0] else ""
|
||||
rows = cur.execute("select un, t1 from sh where k = ?", (skey,)).fetchall()
|
||||
un = rows[0][0] if rows and rows[0] else ""
|
||||
|
||||
if not un:
|
||||
raise Pebkac(400, "that sharekey didn't match anything")
|
||||
|
||||
expiry = rows[0][1]
|
||||
|
||||
if un != self.uname and self.uname != self.args.shr_adm:
|
||||
t = "your username (%r) does not match the sharekey's owner (%r) and you're not admin"
|
||||
raise Pebkac(400, t % (self.uname, un))
|
||||
|
||||
cur.execute("delete from sh where k = ?", (skey,))
|
||||
reload = False
|
||||
act = self.uparam["eshare"]
|
||||
if act == "rm":
|
||||
cur.execute("delete from sh where k = ?", (skey,))
|
||||
if skey in self.asrv.vfs.nodes[self.args.shr.strip("/")].nodes:
|
||||
reload = True
|
||||
else:
|
||||
now = time.time()
|
||||
if expiry < now:
|
||||
expiry = now
|
||||
reload = True
|
||||
expiry += int(act) * 60
|
||||
cur.execute("update sh set t1 = ? where k = ?", (expiry, skey))
|
||||
|
||||
cur.connection.commit()
|
||||
if reload:
|
||||
self.conn.hsrv.broker.ask("_reload_blocking", False, False).get()
|
||||
self.conn.hsrv.broker.ask("up2k.wake_rescanner").get()
|
||||
|
||||
self.redirect(self.args.SRS + "?shares")
|
||||
return True
|
||||
|
||||
@@ -106,6 +106,7 @@ class SvcHub(object):
|
||||
self.no_ansi = args.no_ansi
|
||||
self.logf: Optional[typing.TextIO] = None
|
||||
self.logf_base_fn = ""
|
||||
self.is_dut = False # running in unittest; always False
|
||||
self.stop_req = False
|
||||
self.stopping = False
|
||||
self.stopped = False
|
||||
|
||||
@@ -403,7 +403,7 @@ class Tftpd(object):
|
||||
bos.stat(ap)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
return vpath == "/"
|
||||
|
||||
def _p_isdir(self, vpath: str) -> bool:
|
||||
try:
|
||||
@@ -411,7 +411,7 @@ class Tftpd(object):
|
||||
ret = stat.S_ISDIR(st.st_mode)
|
||||
return ret
|
||||
except:
|
||||
return False
|
||||
return vpath == "/"
|
||||
|
||||
def _hook(self, *a: Any, **ka: Any) -> None:
|
||||
src = inspect.currentframe().f_back.f_code.co_name
|
||||
|
||||
@@ -236,6 +236,9 @@ class Up2k(object):
|
||||
if not self.pp and self.args.exit == "idx":
|
||||
return self.hub.sigterm()
|
||||
|
||||
if self.hub.is_dut:
|
||||
return
|
||||
|
||||
Daemon(self._snapshot, "up2k-snapshot")
|
||||
if have_e2d:
|
||||
Daemon(self._hasher, "up2k-hasher")
|
||||
@@ -432,7 +435,7 @@ class Up2k(object):
|
||||
def _sched_rescan(self) -> None:
|
||||
volage = {}
|
||||
cooldown = timeout = time.time() + 3.0
|
||||
while True:
|
||||
while not self.stop:
|
||||
now = time.time()
|
||||
timeout = max(timeout, cooldown)
|
||||
wait = timeout - time.time()
|
||||
@@ -440,6 +443,9 @@ class Up2k(object):
|
||||
with self.rescan_cond:
|
||||
self.rescan_cond.wait(wait)
|
||||
|
||||
if self.stop:
|
||||
return
|
||||
|
||||
now = time.time()
|
||||
if now < cooldown:
|
||||
# self.log("SR: cd - now = {:.2f}".format(cooldown - now), 5)
|
||||
@@ -463,6 +469,7 @@ class Up2k(object):
|
||||
if self.args.shr:
|
||||
timeout = min(self._check_shares(), timeout)
|
||||
except Exception as ex:
|
||||
timeout = min(timeout, now + 60)
|
||||
t = "could not check for expiring shares: %r"
|
||||
self.log(t % (ex,), 1)
|
||||
|
||||
@@ -572,27 +579,53 @@ class Up2k(object):
|
||||
|
||||
now = time.time()
|
||||
timeout = now + 9001
|
||||
maxage = self.args.shr_rt * 60
|
||||
low = now - maxage
|
||||
|
||||
vn = self.asrv.vfs.nodes.get(self.args.shr.strip("/"))
|
||||
active = vn and vn.nodes
|
||||
|
||||
db = sqlite3.connect(self.args.shr_db, timeout=2)
|
||||
cur = db.cursor()
|
||||
|
||||
q = "select k from sh where t1 and t1 <= ?"
|
||||
rm = [x[0] for x in cur.execute(q, (now,))]
|
||||
rm = [x[0] for x in cur.execute(q, (now,))] if active else []
|
||||
if rm:
|
||||
assert vn and vn.nodes # type: ignore
|
||||
# self.log("chk_shr: %d" % (len(rm),))
|
||||
zss = set(rm)
|
||||
rm = [zs for zs in vn.nodes if zs in zss]
|
||||
reload = bool(rm)
|
||||
if reload:
|
||||
self.log("disabling expired shares %s" % (rm,))
|
||||
|
||||
rm = [x[0] for x in cur.execute(q, (low,))]
|
||||
if rm:
|
||||
self.log("forgetting expired shares %s" % (rm,))
|
||||
cur.executemany("delete from sh where k=?", [(x,) for x in rm])
|
||||
cur.executemany("delete from sf where k=?", [(x,) for x in rm])
|
||||
db.commit()
|
||||
|
||||
if reload:
|
||||
Daemon(self.hub._reload_blocking, "sharedrop", (False, False))
|
||||
|
||||
q = "select min(t1) from sh where t1 > 1"
|
||||
(earliest,) = cur.execute(q).fetchone()
|
||||
q = "select min(t1) from sh where t1 > ?"
|
||||
(earliest,) = cur.execute(q, (1,)).fetchone()
|
||||
if earliest:
|
||||
timeout = earliest - now
|
||||
# deadline for revoking regular access
|
||||
timeout = min(timeout, earliest + maxage)
|
||||
|
||||
(earliest,) = cur.execute(q, (now - 2,)).fetchone()
|
||||
if earliest:
|
||||
# deadline for revival; drop entirely
|
||||
timeout = min(timeout, earliest)
|
||||
|
||||
cur.close()
|
||||
db.close()
|
||||
|
||||
if self.args.shr_v:
|
||||
self.log("next shr_chk = %d (%d)" % (timeout, timeout - time.time()))
|
||||
|
||||
return timeout
|
||||
|
||||
def _check_xiu(self) -> float:
|
||||
@@ -1405,7 +1438,7 @@ class Up2k(object):
|
||||
if dts == lmod and dsz == sz and (nohash or dw[0] != "#" or not sz):
|
||||
continue
|
||||
|
||||
t = "reindex [{}] => [{}] ({}/{}) ({}/{})".format(
|
||||
t = "reindex [{}] => [{}] mtime({}/{}) size({}/{})".format(
|
||||
top, rp, dts, lmod, dsz, sz
|
||||
)
|
||||
self.log(t)
|
||||
@@ -2664,11 +2697,19 @@ class Up2k(object):
|
||||
if stat.S_ISLNK(st.st_mode):
|
||||
# broken symlink
|
||||
raise Exception()
|
||||
except:
|
||||
if st.st_size != dsize:
|
||||
t = "candidate ignored (db/fs desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}"
|
||||
t = t.format(
|
||||
wark, st.st_size, dsize, st.st_mtime, dtime, dp_abs
|
||||
)
|
||||
self.log(t)
|
||||
raise Exception("desync")
|
||||
except Exception as ex:
|
||||
if n4g:
|
||||
st = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
|
||||
else:
|
||||
lost.append((cur, dp_dir, dp_fn))
|
||||
if str(ex) != "desync":
|
||||
lost.append((cur, dp_dir, dp_fn))
|
||||
continue
|
||||
|
||||
j = {
|
||||
@@ -2726,13 +2767,16 @@ class Up2k(object):
|
||||
ptop = None # use cj or job as appropriate
|
||||
|
||||
if not job and wark in reg:
|
||||
# ensure the files haven't been deleted manually
|
||||
# ensure the files haven't been edited or deleted
|
||||
path = ""
|
||||
st = None
|
||||
rj = reg[wark]
|
||||
names = [rj[x] for x in ["name", "tnam"] if x in rj]
|
||||
for fn in names:
|
||||
path = djoin(rj["ptop"], rj["prel"], fn)
|
||||
try:
|
||||
if bos.path.getsize(path) > 0 or not rj["need"]:
|
||||
st = bos.stat(path)
|
||||
if st.st_size > 0 or not rj["need"]:
|
||||
# upload completed or both present
|
||||
break
|
||||
except:
|
||||
@@ -2743,6 +2787,14 @@ class Up2k(object):
|
||||
del reg[wark]
|
||||
break
|
||||
|
||||
if st and not self.args.nw and not n4g and st.st_size != rj["size"]:
|
||||
t = "will not dedup (fs index desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}"
|
||||
t = t.format(
|
||||
wark, st.st_size, rj["size"], st.st_mtime, rj["lmod"], path
|
||||
)
|
||||
self.log(t)
|
||||
del reg[wark]
|
||||
|
||||
if job or wark in reg:
|
||||
job = job or reg[wark]
|
||||
if (
|
||||
@@ -2850,6 +2902,7 @@ class Up2k(object):
|
||||
return self._handle_json(job, depth + 1)
|
||||
|
||||
job["name"] = self._untaken(pdir, job, now)
|
||||
dst = djoin(job["ptop"], job["prel"], job["name"])
|
||||
|
||||
if not self.args.nw:
|
||||
dvf: dict[str, Any] = vfs.flags
|
||||
|
||||
@@ -1618,7 +1618,12 @@ var LANGS = ["eng", "nor", "chi"];
|
||||
if (window.langmod)
|
||||
langmod();
|
||||
|
||||
var L = Ls[sread("cpp_lang", LANGS) || lang] || Ls.eng || Ls.nor || Ls.chi;
|
||||
for (var a = LANGS.length; a > 0;)
|
||||
if (!Ls[LANGS[--a]])
|
||||
LANGS.splice(a, 1);
|
||||
|
||||
var L = Ls[sread("cpp_lang", LANGS) || lang] ||
|
||||
Ls.eng || Ls.nor || Ls.chi;
|
||||
|
||||
for (var a = 0; a < LANGS.length; a++) {
|
||||
for (var b = a + 1; b < LANGS.length; b++) {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<th>expires</th>
|
||||
<th>min</th>
|
||||
<th>hrs</th>
|
||||
<th>add time</th>
|
||||
</tr></thead><tbody>
|
||||
{% for k, pw, vp, pr, st, un, t0, t1 in rows %}
|
||||
<tr>
|
||||
@@ -45,8 +46,9 @@
|
||||
<td>{{ un|e }}</td>
|
||||
<td>{{ t0 }}</td>
|
||||
<td>{{ t1 }}</td>
|
||||
<td>{{ ((t1 - now) / 60) | round(1) if t1 else "inf" }}</td>
|
||||
<td>{{ ((t1 - now) / 3600) | round(1) if t1 else "inf" }}</td>
|
||||
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 60) | round(1) }}</td>
|
||||
<td>{{ "inf" if not t1 else "dead" if t1 < now else ((t1 - now) / 3600) | round(1) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
|
||||
@@ -3,7 +3,17 @@ for (var a = 0; a < t.length; a++)
|
||||
t[a].onclick = rm;
|
||||
|
||||
function rm() {
|
||||
var u = SR + shr + uricom_enc(this.getAttribute('k')) + '?unshare',
|
||||
var u = SR + shr + uricom_enc(this.getAttribute('k')) + '?eshare=rm',
|
||||
xhr = new XHR();
|
||||
|
||||
xhr.open('POST', u, true);
|
||||
xhr.onload = xhr.onerror = cb;
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function bump() {
|
||||
var k = this.closest('tr').getElementsByTagName('a')[0].getAttribute('k'),
|
||||
u = SR + shr + uricom_enc(k) + '?eshare=' + this.value,
|
||||
xhr = new XHR();
|
||||
|
||||
xhr.open('POST', u, true);
|
||||
@@ -34,4 +44,13 @@ function cb() {
|
||||
tr[a].cells[b].innerHTML =
|
||||
v ? unix2iso(v).replace(' ', ', ') : 'never';
|
||||
}
|
||||
|
||||
for (var a = 0; a < tr.length; a++)
|
||||
tr[a].cells[11].innerHTML =
|
||||
'<button value="1">1min</button> ' +
|
||||
'<button value="60">1h</button>';
|
||||
|
||||
var btns = QSA('td button'), aa = btns.length;
|
||||
for (var a = 0; a < aa; a++)
|
||||
btns[a].onclick = bump;
|
||||
})();
|
||||
|
||||
@@ -81,12 +81,11 @@ var Ls = {
|
||||
}
|
||||
};
|
||||
|
||||
var LANGS = ["eng", "nor", "chi"];
|
||||
|
||||
if (window.langmod)
|
||||
langmod();
|
||||
|
||||
var d = Ls[sread("cpp_lang", LANGS) || lang] || Ls.eng || Ls.nor || Ls.chi;
|
||||
var d = Ls[sread("cpp_lang", Object.keys(Ls)) || lang] ||
|
||||
Ls.eng || Ls.nor || Ls.chi;
|
||||
|
||||
for (var k in (d || {})) {
|
||||
var f = k.slice(-1),
|
||||
|
||||
@@ -385,6 +385,7 @@ html.y textarea:focus {
|
||||
}
|
||||
.mdo pre,
|
||||
.mdo code,
|
||||
.mdo code[class*="language-"],
|
||||
.mdo tt {
|
||||
font-family: 'scp', monospace, monospace;
|
||||
font-family: var(--font-mono), 'scp', monospace, monospace;
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2024-0823-2307 `v1.14.2` bing chilling
|
||||
|
||||
## new features
|
||||
|
||||
* #94 @ultwcz translated the UI to Chinese (thx!) 92edea1d
|
||||
* #84 improvements to [shares](https://github.com/9001/copyparty#shares): 8122dded
|
||||
* if one or more files are selected for sharing, they are placed into a virtual folder
|
||||
* more appropriate password UI for accessing protected shares
|
||||
* human-readable timestamps in shares listing
|
||||
* u2c (commandline uploader): support multiple exclusion patterns f356faa2
|
||||
|
||||
## bugfixes
|
||||
|
||||
* remove confusing logmessage when downloading a zerobyte file 9f034d9c
|
||||
* shares: 7ff46966
|
||||
* fix crash if the root volume is unmapped
|
||||
* log-spam on config reload
|
||||
* password coalescing
|
||||
* add chrome support
|
||||
|
||||
## other changes
|
||||
|
||||
* #93 add html IDs to the tabstrip 461f3158
|
||||
|
||||
|
||||
|
||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
# 2024-0819-0014 `v1.14.1` one step forward
|
||||
|
||||
|
||||
@@ -176,7 +176,8 @@ authenticate using header `Cookie: cppwd=foo` or url param `&pw=foo`
|
||||
| mPOST | `?media` | `f=FILE` | ...and return medialink (not hotlink) |
|
||||
| mPOST | | `act=mkdir`, `name=foo` | create directory `foo` at URL |
|
||||
| POST | `?delete` | | delete URL recursively |
|
||||
| POST | `?unshare` | | stop sharing a file/folder |
|
||||
| POST | `?eshare=rm` | | stop sharing a file/folder |
|
||||
| POST | `?eshare=3` | | set expiration to 3 minutes |
|
||||
| jPOST | `?share` | (complicated) | create temp URL for file/folder |
|
||||
| jPOST | `?delete` | `["/foo","/bar"]` | delete `/foo` and `/bar` recursively |
|
||||
| uPOST | | `msg=foo` | send message `foo` into server log |
|
||||
|
||||
220
docs/logo.svg
Normal file
220
docs/logo.svg
Normal file
@@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="300mm"
|
||||
height="207mm"
|
||||
viewBox="0 0 300 207"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<title
|
||||
id="title1">copyparty_logo</title>
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1">
|
||||
<stop
|
||||
style="stop-color:#ffcc55;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop1" />
|
||||
<stop
|
||||
style="stop-color:#ffcc00;stop-opacity:1"
|
||||
offset="0.2"
|
||||
id="stop2" />
|
||||
<stop
|
||||
style="stop-color:#ff8800;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1"
|
||||
id="linearGradient2"
|
||||
x1="15"
|
||||
y1="15"
|
||||
x2="15"
|
||||
y2="143"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="the logo of https://github.com/9001/copyparty">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>copyparty_logo</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="kassett">
|
||||
<rect
|
||||
style="fill:#333333"
|
||||
id="rect1"
|
||||
width="300"
|
||||
height="205"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="12"
|
||||
ry="12" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient2)"
|
||||
id="rect2"
|
||||
width="270"
|
||||
height="128"
|
||||
x="15"
|
||||
y="15"
|
||||
rx="8"
|
||||
ry="8" />
|
||||
<rect
|
||||
style="fill:#333333"
|
||||
id="rect3"
|
||||
width="172"
|
||||
height="52"
|
||||
x="64"
|
||||
y="72"
|
||||
rx="26"
|
||||
ry="26" />
|
||||
<circle
|
||||
style="fill:#cccccc"
|
||||
id="circle1"
|
||||
cx="91"
|
||||
cy="98"
|
||||
r="18" />
|
||||
<circle
|
||||
style="fill:#cccccc"
|
||||
id="circle2"
|
||||
cx="209"
|
||||
cy="98"
|
||||
r="18" />
|
||||
<path
|
||||
style="fill:#737373;stroke-width:1px"
|
||||
d="m 49,207 6.64,-39.33 c 1.6,-6.3 6.1,-7.7 11.55,-8 58.8,-1.1 106.3,-0.76 165,0 7.4,0.11 10.2,3.25 11.5,8.7 L 251,207 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="skeu"
|
||||
style="display:none">
|
||||
<path
|
||||
style="fill:#555555;stroke-width:1px"
|
||||
d="m 48.7,207 6.66,-36.87 c 1.6,-5.9 6.1,-7.2 11.6,-7.5 58.9,-1.06 106.6,-0.7 165.5,0 7.4,0.11 10.2,3.05 11.5,8.12 L 251.3,207 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="tekst"
|
||||
style="display:none">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:38.8056px;line-height:1.25;font-family:Akbar;-inkscape-font-specification:Akbar;letter-spacing:3.70417px;word-spacing:0px;fill:#333333"
|
||||
x="47.153069"
|
||||
y="55.548954"
|
||||
id="text1"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1"
|
||||
x="47.153069"
|
||||
y="55.548954"
|
||||
style="-inkscape-font-specification:Akbar"
|
||||
rotate="0 0">copyparty</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="stensatt">
|
||||
<path
|
||||
d="m 64.54,50.93 q -0.85,0.93 -4.73,2.3 -3.6,1.3 -4.4,1.3 -3.3,0 -5.1,-2.1 -1.75,-2 -1.75,-5.36 0,-4.6 3.76,-7.64 3.3,-2.7 7.3,-2.7 0.4,0 0.93,0.74 0.54,0.7 0.54,1.16 0,2.06 -2.2,2.7 -1.36,0.4 -4.04,1.16 -2.2,1.16 -2.2,4.4 0,3.2 2.9,3.2 0.85,0 0.85,0 0.54,0 1.44,-0.16 1.1,-0.23 2.9,-0.74 1.8,-0.54 2.13,-0.54 0.4,0 1.75,0.6 z"
|
||||
style="fill:#333333"
|
||||
id="path11" />
|
||||
<path
|
||||
d="m 89.2,45.03 q 0,4.2 -3.7,6.95 -3.2,2.3 -6.87,2.3 -3.4,0 -6,-2.6 -2.5,-2.6 -2.5,-6 0,-3.6 3.14,-6.64 3.2,-3 6.8,-3 3.5,0 6.3,2.76 2.83,2.76 2.83,6.25 z m -3.4,0.16 q 0,-2.25 -1.75,-3.7 -1.7,-1.5 -4,-1.5 -0.1,0 -1.6,1.6 -1.44,1.55 -2.44,1.55 -0.6,0 -0.8,-0.3 -1.16,2.3 -1.16,3 0,2.25 2.13,3.4 1.6,0.9 3.6,0.9 2,0 3.76,-1.1 2.25,-1.4 2.25,-3.84 z"
|
||||
style="fill:#333333"
|
||||
id="path12" />
|
||||
<path
|
||||
d="m 114.3,47.94 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.66,1.5 -0.74,0 -2.7,-0.4 -1.9,-0.4 -2.64,-0.4 -2,0 -2,2.64 0,0.85 0.2,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.74,2.83 -1.44,0 -3,-0.85 Q 95.6,53.34 95.6,50.9 q 0,-3.65 1.75,-8.1 2.37,-6.05 6.4,-6.05 3.7,0 7.26,4.1 3.3,3.84 3.3,7.14 z m -3.76,0.2 q -0.66,-2.2 -2.64,-4.4 -2.25,-2.5 -4.3,-2.5 -1.3,0 -2.3,2.2 -0.85,1.8 -0.85,3.26 0,0.47 0.4,1.24 0.4,0.8 0.8,0.8 1.05,0 3.14,0.3 2.13,0.3 3.2,0.3 0.35,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
|
||||
style="fill:#333333"
|
||||
id="path13" />
|
||||
<path
|
||||
d="m 133.6,40.2 q -2.06,4.1 -3.2,7 -0.1,0.3 -1.55,4.5 -0.47,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.37,2.64 -1.4,-0.2 -1.55,-1.6 -0.04,-0.2 -0.04,-0.5 0,-0.16 0.3,-1.5 1.05,-5.04 1.05,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.44,-1.36 2.1,-1.36 0.4,0 1.05,0.6 0.6,0.6 0.7,1.1 0.85,6.2 4.9,11.1 1,-1.8 1.86,-4.04 0.5,-1.4 1.6,-4.15 1.86,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.85,0.3 1.24,2.8 z"
|
||||
style="fill:#333333"
|
||||
id="path14" />
|
||||
<path
|
||||
d="m 156.97,47.94 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.66,1.5 -0.74,0 -2.7,-0.4 -1.9,-0.4 -2.64,-0.4 -2,0 -2,2.64 0,0.85 0.2,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.74,2.83 -1.44,0 -3,-0.85 -1.44,-9.5 -1.44,-11.95 0,-3.65 1.75,-8.1 2.37,-6.05 6.4,-6.05 3.7,0 7.26,4.1 3.3,3.84 3.3,7.14 z m -3.76,0.2 q -0.66,-2.2 -2.64,-4.4 -2.25,-2.5 -4.3,-2.5 -1.3,0 -2.3,2.2 -0.85,1.8 -0.85,3.26 0,0.47 0.4,1.24 0.4,0.8 0.8,0.8 1.05,0 3.14,0.3 2.13,0.3 3.2,0.3 0.35,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
|
||||
style="fill:#333333"
|
||||
id="path15" />
|
||||
<path
|
||||
d="m 182.4,53.3 q 0,0.9 -0.6,1.5 -0.54,0.6 -1.4,0.6 -1.55,0 -2.95,-0.9 -1.4,-0.93 -2.13,-2.3 -0.74,-0.1 -1.5,0.85 -0.9,1.16 -1.05,1.24 -1.24,0.54 -3.9,0.54 -2.2,0 -3.9,-2.44 -1.5,-2.13 -1.5,-4 0,-3.4 3.34,-6.4 3.2,-2.9 6.7,-2.9 0.9,0 1.67,0.6 0.8,0.6 0.8,1.44 0,0.54 -0.35,1.1 2.37,0.9 2.37,2.83 0,0.35 -0.1,1.05 -0.1,0.7 -0.1,1.05 0,0.4 0.1,0.6 0.5,1.3 2.56,3.4 1.9,1.9 1.9,2.2 z m -8.1,-10.1 q -0.4,0 -1.1,-0.1 -0.74,-0.16 -1.1,-0.16 -1.3,0 -3.2,1.94 -1.9,1.94 -1.9,3.3 0,0.8 0.7,1.8 0.9,1.3 2.2,1.3 2.64,0 3.53,-2.9 0.5,-2.6 1,-5.16 z"
|
||||
style="fill:#333333"
|
||||
id="path16" />
|
||||
<path
|
||||
d="m 204.8,41.35 q -0.4,0.4 -1.5,0.4 -0.85,0 -2.5,-0.3 -1.67,-0.3 -2.5,-0.3 -4.66,0 -5.43,6.9 -0.35,3.1 -0.4,3.3 -0.4,1 -1.7,2.3 h -1.16 q -0.66,-1.2 -1.3,-4.1 -0.6,-2.76 -0.6,-4.27 0,-1.16 0.1,-1.5 0.2,-0.54 1,-0.54 0.3,0 0.66,0.3 0.4,0.3 0.4,0.3 1.9,-3.53 3.07,-4.6 1.8,-1.7 5.04,-1.7 1.4,0 3.6,0.9 2.83,1.16 3.3,2.8 z"
|
||||
style="fill:#333333"
|
||||
id="path17" />
|
||||
<path
|
||||
d="m 228.46,37.16 q 0.3,0.8 0.3,1.44 0,1.86 -2.4,1.86 -1,0 -3.45,-0.5 -2.5,-0.54 -3.45,-0.54 -1.24,0 -1.44,0.1 -0.47,0.2 -0.47,1.2 0,2.2 0.6,6.9 0.74,5.86 1.6,6.13 -0.35,0.35 -0.35,1.1 -1.24,0.7 -2.64,0.7 -1.4,0 -1.94,-3.9 -0.2,-1.36 -0.5,-7.76 -0.23,-4.6 -0.8,-5.5 -0.3,-0.47 -4.35,-0.35 -1,0.04 -1.55,0.1 -0.54,0 -0.35,0 -0.8,0 -1.2,-0.7 -0.54,-1.3 -0.54,-1.4 0,-1.44 4.15,-2 1.6,-0.16 4.73,-0.5 0,-0.85 -0.1,-2.56 -0.04,-1.75 -0.04,-2.6 0,-4.35 2.1,-4.35 0.54,0 1.1,0.6 0.6,0.6 0.6,1.1 v 7.9 q 1.1,1.2 5,1.7 3.9,0.5 5.3,1.86 z"
|
||||
style="fill:#333333"
|
||||
id="path18" />
|
||||
<path
|
||||
d="m 250.2,40.2 q -2.06,4.1 -3.2,7 -0.1,0.3 -1.55,4.5 -0.47,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.37,2.64 -1.4,-0.2 -1.55,-1.6 -0.04,-0.2 -0.04,-0.5 0,-0.16 0.3,-1.5 1.05,-5.04 1.05,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.44,-1.36 2.1,-1.36 0.4,0 1.05,0.6 0.6,0.6 0.7,1.1 0.85,6.2 4.9,11.1 1,-1.8 1.86,-4.04 0.5,-1.4 1.6,-4.15 1.86,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.85,0.3 1.24,2.8 z"
|
||||
style="fill:#333333"
|
||||
id="path19" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="tagger">
|
||||
<g
|
||||
id="g1">
|
||||
<path
|
||||
id="path4"
|
||||
style="fill:#333333"
|
||||
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
<path
|
||||
id="path5"
|
||||
style="fill:#333333"
|
||||
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
<path
|
||||
id="path6"
|
||||
style="fill:#333333"
|
||||
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
</g>
|
||||
<g
|
||||
id="g2"
|
||||
transform="rotate(30,150,318.19)">
|
||||
<path
|
||||
id="path7"
|
||||
style="fill:#333333"
|
||||
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
<path
|
||||
id="path8"
|
||||
style="fill:#333333"
|
||||
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
<path
|
||||
id="path9"
|
||||
style="fill:#333333"
|
||||
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
|
||||
sodipodi:nodetypes="cccccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -3,6 +3,7 @@ set -e
|
||||
echo
|
||||
|
||||
berr() { p=$(head -c 72 </dev/zero | tr '\0' =); printf '\n%s\n\n' $p; cat; printf '\n%s\n\n' $p; }
|
||||
aerr() { printf '%s\n' "$*" | berr; }
|
||||
|
||||
help() { exec cat <<'EOF'
|
||||
|
||||
@@ -28,9 +29,11 @@ help() { exec cat <<'EOF'
|
||||
#
|
||||
# `no-tfp` saves ~10k by removing the tftp server, disabling --tftp
|
||||
#
|
||||
# `no-zm` saves ~7k by removing the zeroconf mDNS server
|
||||
#
|
||||
# `no-smb` saves ~3.5k by removing the smb / cifs server
|
||||
#
|
||||
# `no-zm` saves ~k by removing the zeroconf mDNS server
|
||||
# `no-pf` saves ~2.8k by removing the option to download partyfuse
|
||||
#
|
||||
# _____________________________________________________________________
|
||||
# web features:
|
||||
@@ -52,10 +55,15 @@ help() { exec cat <<'EOF'
|
||||
#
|
||||
# `ign-wd` allows building an sfx without webdeps
|
||||
#
|
||||
# ---------------------------------------------------------------------
|
||||
#
|
||||
# _____________________________________________________________________
|
||||
# if you are on windows, you can use msys2:
|
||||
# PATH=/c/Users/$USER/AppData/Local/Programs/Python/Python310:"$PATH" ./make-sfx.sh fast
|
||||
#
|
||||
# _____________________________________________________________________
|
||||
# some usage examples:
|
||||
# ./scripts/make-sfx.sh lang eng no-cm no-hl no-dd no-fnt no-smb no-pf
|
||||
# ./scripts/rls.sh sfx lang eng no-cm no-hl no-dd no-fnt no-smb no-pf
|
||||
# (reduces v1.14.2 from 700k to 495k)
|
||||
|
||||
EOF
|
||||
}
|
||||
@@ -112,6 +120,7 @@ while [ ! -z "$1" ]; do
|
||||
no-tfp) no_tfp=1 ; ;;
|
||||
no-smb) no_smb=1 ; ;;
|
||||
no-zm) no_zm=1 ; ;;
|
||||
no-pf) no_pf=1 ; ;;
|
||||
no-fnt) no_fnt=1 ; ;;
|
||||
no-hl) no_hl=1 ; ;;
|
||||
no-dd) no_dd=1 ; ;;
|
||||
@@ -119,7 +128,6 @@ while [ ! -z "$1" ]; do
|
||||
dl-wd) dl_wd=1 ; ;;
|
||||
ign-wd) ign_wd=1 ; ;;
|
||||
fast) zopf= ; ;;
|
||||
ultra) ultra=1 ; ;;
|
||||
lang) shift;langs="$1"; ;;
|
||||
*) help ; ;;
|
||||
esac
|
||||
@@ -428,6 +436,9 @@ rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
||||
[ $no_zm ] &&
|
||||
rm -rf copyparty/mdns.py copyparty/stolen/dnslib
|
||||
|
||||
[ $no_pf ] &&
|
||||
rm -rf copyparty/web/a/partyfuse.py
|
||||
|
||||
[ $no_cm ] && {
|
||||
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
||||
echo h > copyparty/web/mde.html
|
||||
@@ -451,11 +462,16 @@ rm -f ftp/pyftpdlib/{__main__,prefork}.py
|
||||
ised 's/(cursor: ?)url\([^)]+\), ?(pointer)/\1\2/; s/[0-9]+% \{cursor:[^}]+\}//; s/animation: ?cursor[^};]+//' $f
|
||||
}
|
||||
|
||||
[ $langs ] &&
|
||||
[ $langs ] && {
|
||||
echo $langs | grep -q eng || {
|
||||
langs="eng|$langs"
|
||||
aerr "ERROR: removing english is not supported; will do this instead: $langs"
|
||||
}
|
||||
for f in copyparty/web/{browser.js,splash.js}; do
|
||||
gzip -d "$f.gz" || true
|
||||
iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f
|
||||
iawk '/^\}/{l=0} !l; /^var Ls =/{l=1;next} !l{next} o; /^\t["}]/{o=0} /^\t"'"$langs"'"/{o=1;print}' $f
|
||||
done
|
||||
}
|
||||
|
||||
[ ! $repack ] && {
|
||||
# uncomment
|
||||
|
||||
@@ -34,6 +34,6 @@ d1420c8417fad7888766dd26b9706a87c63e8f33dceeb8e26d0056d5127b0b3ed9272e44b4b76113
|
||||
8a6e2b13a2ec4ef914a5d62aad3db6464d45e525a82e07f6051ed10474eae959069e165dba011aefb8207cdfd55391d73d6f06362c7eb247b08763106709526e mutagen-1.47.0-py3-none-any.whl
|
||||
0203ec2551c4836696cfab0b2c9fff603352f03fa36e7476e2e1ca7ec57a3a0c24bd791fcd92f342bf817f0887854d9f072e0271c643de4b313d8c9569ba8813 packaging-24.1-py3-none-any.whl
|
||||
2be320b4191f208cdd6af183c77ba2cf460ea52164ee45ac3ff17d6dfa57acd9deff016636c2dd42a21f4f6af977d5f72df7dacf599bebcf41757272354d14c1 pillow-10.4.0-cp312-cp312-win_amd64.whl
|
||||
776378f5414efd26ec8a1cb3228a7b5fdf6afca3fa335a0e9b071266d55d9d9e66ee157c25a468a05bfa70ccd33c48b101998523fc6ff6bcf5e82a1d81ed0af8 pyinstaller-6.9.0-py3-none-win_amd64.whl
|
||||
c0af77d2a57cb063ab038dc986ed3582bc5acc8c8bd91d726101935d6388f50854ddbca26bc846ed5d1022cdee4d96242938c66f0ddc4565c36b60d691064db8 pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl
|
||||
896ddddbd4b85e86e0600cb65eb4c07fbc7f3802d47e7f660411e20b5500831469b97ed4770f25820f4e75cbfac40308da624fd86d4f62e578149d5c276a9cde pyinstaller-6.10.0-py3-none-win_amd64.whl
|
||||
873781decaeef07f6a79b0ed8b9f35f3fa534a1ea0d866991e40278a10818fa5b60c70b0d5828971b045364f1099694cd1e5d5d60d480acb93fcfbfbced4a09e pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
||||
0572c6345f6a4f7f3e5c2ff858e3ca7ca54ae4478f3d59d8e18cb0f596e61dcf12aef579db229e83d63b30f15d6684ee6bb3feaea9413e5e636a503933057678 python-3.12.5-amd64.exe
|
||||
|
||||
@@ -39,8 +39,8 @@ fns=(
|
||||
mutagen-1.47.0-py3-none-any.whl
|
||||
packaging-24.1-py3-none-any.whl
|
||||
pillow-10.4.0-cp312-cp312-win_amd64.whl
|
||||
pyinstaller-6.9.0-py3-none-win_amd64.whl
|
||||
pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl
|
||||
pyinstaller-6.10.0-py3-none-win_amd64.whl
|
||||
pyinstaller_hooks_contrib-2024.8-py3-none-any.whl
|
||||
python-3.12.5-amd64.exe
|
||||
)
|
||||
[ $w7 ] && fns+=( # u2c stuff
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# if specified, keep the following sfx flags last: gz gzz fast
|
||||
|
||||
parallel=1
|
||||
|
||||
[ -e make-sfx.sh ] || cd scripts
|
||||
@@ -35,6 +37,14 @@ f=../dist/copyparty-sfx
|
||||
|
||||
$f$s.py --version >/dev/null
|
||||
|
||||
while [ "$1" ]; do
|
||||
case "$1" in
|
||||
gz*) break;;
|
||||
fast) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ $parallel -gt 1 ] && {
|
||||
printf '\033[%s' s 2r H "0;1;37;44mbruteforcing sfx size -- press enter to terminate" K u "7m $* " K $'27m\n'
|
||||
trap "rm -f .sfx-run; printf '\033[%s' s r u" INT TERM EXIT
|
||||
|
||||
138
tests/test_dedup.py
Normal file
138
tests/test_dedup.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
# coding: utf-8
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from itertools import product
|
||||
|
||||
from copyparty.authsrv import AuthSrv
|
||||
from copyparty.httpcli import HttpCli
|
||||
from tests import util as tu
|
||||
from tests.util import Cfg
|
||||
|
||||
|
||||
class TestDedup(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.td = tu.get_ramdisk()
|
||||
|
||||
def tearDown(self):
|
||||
os.chdir(tempfile.gettempdir())
|
||||
shutil.rmtree(self.td)
|
||||
|
||||
def reset(self):
|
||||
td = os.path.join(self.td, "vfs")
|
||||
if os.path.exists(td):
|
||||
shutil.rmtree(td)
|
||||
os.mkdir(td)
|
||||
os.chdir(td)
|
||||
return td
|
||||
|
||||
def test(self):
|
||||
quick = True # sufficient for regular smoketests
|
||||
# quick = False
|
||||
|
||||
dirnames = ["d1", "d2"]
|
||||
filenames = ["f1", "f2"]
|
||||
files = [
|
||||
(
|
||||
"one",
|
||||
"BfcDQQeKz2oG1CPSFyD5ZD1flTYm2IoCY23DqeeVgq6w",
|
||||
"XMbpLRqVdtGmgggqjUI6uSoNMTqZVX4K6zr74XA1BRKc",
|
||||
),
|
||||
(
|
||||
"two",
|
||||
"ko1Q0eJNq3zKYs_oT83Pn8aVFgonj5G1wK8itwnYL4qj",
|
||||
"fxvihWlnQIbVbUPr--TxyV41913kPLhXPD1ngXYxDfou",
|
||||
),
|
||||
]
|
||||
# (data, chash, wark)
|
||||
|
||||
# 3072 uploads in total
|
||||
self.ctr = 3072
|
||||
self.conn = None
|
||||
for e2d in [True, False]:
|
||||
for dn1, fn1, f1 in product(dirnames, filenames, files):
|
||||
for dn2, fn2, f2 in product(dirnames, filenames, files):
|
||||
for dn3, fn3, f3 in product(dirnames, filenames, files):
|
||||
self.reset()
|
||||
if self.conn:
|
||||
self.conn.hsrv.hub.up2k.shutdown()
|
||||
self.args = Cfg(v=[".::A"], a=[], e2d=e2d)
|
||||
self.asrv = AuthSrv(self.args, self.log)
|
||||
self.conn = tu.VHttpConn(
|
||||
self.args, self.asrv, self.log, b"", True
|
||||
)
|
||||
self.do_post(dn1, fn1, f1, True)
|
||||
self.do_post(dn2, fn2, f2, False)
|
||||
self.do_post(dn3, fn3, f3, False)
|
||||
if quick:
|
||||
break
|
||||
|
||||
def do_post(self, dn, fn, fi, first):
|
||||
print("\n\n# do_post", self.ctr, repr((dn, fn, fi, first)))
|
||||
self.ctr -= 1
|
||||
|
||||
data, chash, wark = fi
|
||||
hs = self.handshake(dn, fn, fi)
|
||||
self.assertEqual(hs["wark"], wark)
|
||||
|
||||
sfn = hs["name"]
|
||||
if sfn == fn:
|
||||
print("using original name " + fn)
|
||||
else:
|
||||
print(fn + " got renamed to " + sfn)
|
||||
if first:
|
||||
raise Exception("wait what")
|
||||
|
||||
if hs["hash"]:
|
||||
self.assertEqual(hs["hash"][0], chash)
|
||||
self.put_chunk(dn, wark, chash, data)
|
||||
elif first:
|
||||
raise Exception("found first; %r, %r" % ((dn, fn, fi), hs))
|
||||
|
||||
h, b = self.curl("%s/%s" % (dn, sfn))
|
||||
self.assertEqual(b, data)
|
||||
|
||||
def handshake(self, dn, fn, fi):
|
||||
hdr = "POST /%s/ HTTP/1.1\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n"
|
||||
msg = {"name": fn, "size": 3, "lmod": 1234567890, "life": 0, "hash": [fi[1]]}
|
||||
buf = json.dumps(msg).encode("utf-8")
|
||||
buf = (hdr % (dn, len(buf))).encode("utf-8") + buf
|
||||
print("HS -->", buf)
|
||||
HttpCli(self.conn.setbuf(buf)).run()
|
||||
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||
print("HS <--", ret)
|
||||
return json.loads(ret[1])
|
||||
|
||||
def put_chunk(self, dn, wark, chash, data):
|
||||
msg = [
|
||||
"POST /%s/ HTTP/1.1" % (dn,),
|
||||
"Connection: close",
|
||||
"Content-Type: application/octet-stream",
|
||||
"Content-Length: 3",
|
||||
"X-Up2k-Hash: " + chash,
|
||||
"X-Up2k-Wark: " + wark,
|
||||
"",
|
||||
data,
|
||||
]
|
||||
buf = "\r\n".join(msg).encode("utf-8")
|
||||
print("PUT -->", buf)
|
||||
HttpCli(self.conn.setbuf(buf)).run()
|
||||
ret = self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||
self.assertEqual(ret[1], "thank")
|
||||
|
||||
def curl(self, url, binary=False):
|
||||
h = "GET /%s HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||
HttpCli(self.conn.setbuf((h % (url,)).encode("utf-8"))).run()
|
||||
if binary:
|
||||
h, b = self.conn.s._reply.split(b"\r\n\r\n", 1)
|
||||
return [h.decode("utf-8"), b]
|
||||
|
||||
return self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
|
||||
|
||||
def log(self, src, msg, c=0):
|
||||
print(msg)
|
||||
@@ -24,6 +24,10 @@ def hdr(query, uname):
|
||||
|
||||
|
||||
class TestDots(unittest.TestCase):
|
||||
def __init__(self, *a, **ka):
|
||||
super(TestDots, self).__init__(*a, **ka)
|
||||
self.is_dut = True
|
||||
|
||||
def setUp(self):
|
||||
self.td = tu.get_ramdisk()
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
@@ -16,9 +15,7 @@ from argparse import Namespace
|
||||
|
||||
import jinja2
|
||||
|
||||
WINDOWS = platform.system() == "Windows"
|
||||
ANYWIN = WINDOWS or sys.platform in ["msys"]
|
||||
MACOS = platform.system() == "Darwin"
|
||||
from copyparty.__init__ import MACOS, WINDOWS, E
|
||||
|
||||
J2_ENV = jinja2.Environment(loader=jinja2.BaseLoader) # type: ignore
|
||||
J2_FILES = J2_ENV.from_string("{{ files|join('\n') }}\nJ2EOT")
|
||||
@@ -42,10 +39,11 @@ if MACOS:
|
||||
# 25% faster; until any tests do symlink stuff
|
||||
|
||||
|
||||
from copyparty.__init__ import E
|
||||
from copyparty.__main__ import init_E
|
||||
from copyparty.broker_thr import BrokerThr
|
||||
from copyparty.ico import Ico
|
||||
from copyparty.u2idx import U2idx
|
||||
from copyparty.up2k import Up2k
|
||||
from copyparty.util import FHC, CachedDict, Garda, Unrecv
|
||||
|
||||
init_E(E)
|
||||
@@ -119,10 +117,10 @@ class Cfg(Namespace):
|
||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||
ka = {}
|
||||
|
||||
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol"
|
||||
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_db_ip no_dedup no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
||||
ka.update(**{k: False for k in ex.split()})
|
||||
|
||||
ex = "dotpart dotsrch hook_v no_dhash no_fastboot no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
|
||||
ex = "dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
|
||||
ka.update(**{k: True for k in ex.split()})
|
||||
|
||||
ex = "ah_cli ah_gen css_browser hist js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua"
|
||||
@@ -137,9 +135,12 @@ class Cfg(Namespace):
|
||||
ex = "db_act k304 loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
||||
ka.update(**{k: 0 for k in ex.split()})
|
||||
|
||||
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname R RS SR"
|
||||
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr ipa html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i shr tcolor textfiles unlist vname xff_src R RS SR"
|
||||
ka.update(**{k: "" for k in ex.split()})
|
||||
|
||||
ex = "ban_403 ban_404 ban_422 ban_pw ban_url"
|
||||
ka.update(**{k: "no" for k in ex.split()})
|
||||
|
||||
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
||||
ka.update(**{k: [] for k in ex.split()})
|
||||
|
||||
@@ -221,11 +222,29 @@ class VSock(object):
|
||||
pass
|
||||
|
||||
|
||||
class VHub(object):
|
||||
def __init__(self, args, asrv, log):
|
||||
self.args = args
|
||||
self.asrv = asrv
|
||||
self.log = log
|
||||
self.is_dut = True
|
||||
self.up2k = Up2k(self)
|
||||
|
||||
|
||||
class VBrokerThr(BrokerThr):
|
||||
def __init__(self, hub):
|
||||
self.hub = hub
|
||||
self.log = hub.log
|
||||
self.args = hub.args
|
||||
self.asrv = hub.asrv
|
||||
|
||||
|
||||
class VHttpSrv(object):
|
||||
def __init__(self, args, asrv, log):
|
||||
self.args = args
|
||||
self.asrv = asrv
|
||||
self.log = log
|
||||
self.hub = None
|
||||
|
||||
self.broker = NullBroker(args, asrv)
|
||||
self.prism = None
|
||||
@@ -252,18 +271,25 @@ class VHttpSrv(object):
|
||||
return self.u2idx
|
||||
|
||||
|
||||
class VHttpSrvUp2k(VHttpSrv):
|
||||
def __init__(self, args, asrv, log):
|
||||
super(VHttpSrvUp2k, self).__init__(args, asrv, log)
|
||||
self.hub = VHub(args, asrv, log)
|
||||
self.broker = VBrokerThr(self.hub)
|
||||
|
||||
|
||||
class VHttpConn(object):
|
||||
def __init__(self, args, asrv, log, buf):
|
||||
def __init__(self, args, asrv, log, buf, use_up2k=False):
|
||||
self.t0 = time.time()
|
||||
self.s = VSock(buf)
|
||||
self.sr = Unrecv(self.s, None) # type: ignore
|
||||
self.aclose = {}
|
||||
self.addr = ("127.0.0.1", "42069")
|
||||
self.args = args
|
||||
self.asrv = asrv
|
||||
self.bans = {}
|
||||
self.freshen_pwd = 0.0
|
||||
self.hsrv = VHttpSrv(args, asrv, log)
|
||||
|
||||
Ctor = VHttpSrvUp2k if use_up2k else VHttpSrv
|
||||
self.hsrv = Ctor(args, asrv, log)
|
||||
self.ico = Ico(args)
|
||||
self.ipa_nm = None
|
||||
self.lf_url = None
|
||||
@@ -279,6 +305,12 @@ class VHttpConn(object):
|
||||
self.u2fh = FHC()
|
||||
|
||||
self.get_u2idx = self.hsrv.get_u2idx
|
||||
self.setbuf(buf)
|
||||
|
||||
def setbuf(self, buf):
|
||||
self.s = VSock(buf)
|
||||
self.sr = Unrecv(self.s, None) # type: ignore
|
||||
return self
|
||||
|
||||
|
||||
if WINDOWS:
|
||||
|
||||
Reference in New Issue
Block a user