diff --git a/copyparty/__main__.py b/copyparty/__main__.py index c5b5e52c..cff92ab0 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -847,6 +847,7 @@ def run_argparse( ap2.add_argument("--no-scandir", action="store_true", help="disable scandir; instead using listdir + stat on each file") ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing before starting the httpd") ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead") + ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc") ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60") ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC") ap2.add_argument("--log-fk", metavar="REGEX", type=u, default="", help="log filekey params for files where path matches REGEX; [\033[32m.\033[0m] (a single dot) = all files") diff --git a/copyparty/broker_mp.py b/copyparty/broker_mp.py index b0bf9883..cb8204ad 100644 --- a/copyparty/broker_mp.py +++ b/copyparty/broker_mp.py @@ -113,6 +113,10 @@ class BrokerMp(object): for p in self.procs: p.q_pend.put((0, dest, [args[0], len(self.procs)])) + elif dest == "set_netdevs": + for p in self.procs: + p.q_pend.put((0, dest, list(args))) + elif dest == "cb_httpsrv_up": self.hub.cb_httpsrv_up() diff --git a/copyparty/broker_mpw.py b/copyparty/broker_mpw.py index 41c6ca7c..1a899e87 100644 --- a/copyparty/broker_mpw.py +++ b/copyparty/broker_mpw.py @@ -97,6 +97,9 @@ class MpWorker(BrokerCli): elif dest == "listen": self.httpsrv.listen(args[0], args[1]) + elif dest == "set_netdevs": + self.httpsrv.set_netdevs(args[0]) + elif dest == "retq": # response from previous ipc call with self.retpend_mutex: diff --git a/copyparty/broker_thr.py b/copyparty/broker_thr.py index 515671c6..e40cd38d 100644 --- a/copyparty/broker_thr.py +++ b/copyparty/broker_thr.py @@ -61,6 +61,10 @@ class BrokerThr(BrokerCli): self.httpsrv.listen(args[0], 1) return + if dest == "set_netdevs": + self.httpsrv.set_netdevs(args[0]) + return + # new ipc invoking managed service in hub obj = self.hub for node in dest.split("."): diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 26bc9809..459bcecf 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -691,6 +691,9 @@ class HttpCli(object): if "reset" in self.uparam: return self.set_cfg_reset() + if "hc" in self.uparam: + return self.tx_svcs() + if "h" in self.uparam: return self.tx_mounts() @@ -2562,6 +2565,32 @@ class HttpCli(object): return True + def tx_svcs(self) -> bool: + aname = re.sub("[^0-9a-zA-Z]+", "", self.args.name) or "a" + ep = self.headers["host"] + host = ep.split(":")[0] + hport = ep[ep.find(":") :] if ":" in ep else "" + rip = ( + host + if self.args.rclone_mdns or not self.args.zm + else self.conn.hsrv.nm.map(self.ip) or host + ) + html = self.j2s( + "svcs", + args=self.args, + accs=bool(self.asrv.acct), + s="s" if self.is_https else "", + rip=rip, + ep=ep, + vp=(self.uparam["hc"] or "").lstrip("/"), + host=host, + hport=hport, + aname=aname, + pw=self.pw or "pw", + ) + self.reply(html.encode("utf-8")) + return True + def tx_mounts(self) -> bool: suf = self.urlq({}, ["h"]) avol = [x for x in self.wvol if x in self.rvol] diff --git a/copyparty/httpsrv.py b/copyparty/httpsrv.py index fb3eb0c8..60835d6e 100644 --- a/copyparty/httpsrv.py +++ b/copyparty/httpsrv.py @@ -37,6 +37,7 @@ from .util import ( Daemon, Garda, Magician, + NetMap, ipnorm, min_ex, shut_socket, @@ -72,6 +73,7 @@ class HttpSrv(object): nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else "" self.magician = Magician() + self.nm = NetMap([], {}) self.ssdp: Optional["SSDPr"] = None self.gpwd = Garda(self.args.ban_pw) self.g404 = Garda(self.args.ban_404) @@ -102,10 +104,8 @@ class HttpSrv(object): env = jinja2.Environment() env.loader = jinja2.FileSystemLoader(os.path.join(self.E.mod, "web")) - self.j2 = { - x: env.get_template(x + ".html") - for x in ["splash", "browser", "browser2", "msg", "md", "mde", "cf"] - } + jn = ["splash", "svcs", "browser", "browser2", "msg", "md", "mde", "cf"] + self.j2 = {x: env.get_template(x + ".html") for x in jn} zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz") self.prism = os.path.exists(zs) @@ -140,6 +140,9 @@ class HttpSrv(object): except: pass + def set_netdevs(self, netdevs: dict[str, str]) -> None: + self.nm = NetMap([self.ip], netdevs) + def start_threads(self, n: int) -> None: self.tp_nthr += n if self.args.log_htp: diff --git a/copyparty/mdns.py b/copyparty/mdns.py index 64dd97e6..15b50c28 100644 --- a/copyparty/mdns.py +++ b/copyparty/mdns.py @@ -257,7 +257,12 @@ class MDNS(MCast): self.log("sendto failed: {} ({})".format(srv.ip, ex), "90") def run(self) -> None: - bound = self.create_servers() + try: + bound = self.create_servers() + except: + self.log("no server IP matches the mdns config", 1) + bound = [] + if not bound: self.log("failed to announce copyparty services on the network", 3) return diff --git a/copyparty/multicast.py b/copyparty/multicast.py index 2d121d65..f61a211b 100644 --- a/copyparty/multicast.py +++ b/copyparty/multicast.py @@ -20,6 +20,10 @@ if not hasattr(socket, "IPPROTO_IPV6"): setattr(socket, "IPPROTO_IPV6", 41) +class NoIPs(Exception): + pass + + class MC_Sck(object): """there is one socket for each server ip""" @@ -114,7 +118,7 @@ class MCast(object): ips = [x for x in ips if ":" not in x or x.startswith("fe80")] if not ips: - raise Exception("no server IP matches the mdns config") + raise NoIPs() for ip in ips: v6 = ":" in ip @@ -302,8 +306,8 @@ class MCast(object): t = "could not map client {} to known subnet; maybe forwarded from another network?" self.log(t.format(cip), 3) - self.cscache[cip] = ret if len(self.cscache) > 9000: self.cscache = {} + self.cscache[cip] = ret return ret diff --git a/copyparty/ssdp.py b/copyparty/ssdp.py index c24a218c..0fa7511f 100644 --- a/copyparty/ssdp.py +++ b/copyparty/ssdp.py @@ -99,7 +99,12 @@ class SSDPd(MCast): self.log_func("SSDP", msg, c) def run(self) -> None: - bound = self.create_servers() + try: + bound = self.create_servers() + except: + self.log("no server IP matches the ssdp config", 1) + bound = [] + if not bound: self.log("failed to announce copyparty services on the network", 3) return diff --git a/copyparty/tcpsrv.py b/copyparty/tcpsrv.py index 77d50767..1188e9aa 100644 --- a/copyparty/tcpsrv.py +++ b/copyparty/tcpsrv.py @@ -252,6 +252,7 @@ class TcpSrv(object): self.srv = srvs self.bound = bound self.nsrv = len(srvs) + self.hub.broker.say("set_netdevs", self.netdevs) def shutdown(self) -> None: self.stopping = True diff --git a/copyparty/util.py b/copyparty/util.py index 6ec54fe7..eafd3e12 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -26,10 +26,10 @@ from collections import Counter from datetime import datetime from email.utils import formatdate -from ipaddress import IPv6Address +from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network from queue import Queue -from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, VT100, WINDOWS, unicode +from .__init__ import ANYWIN, MACOS, PY2, TYPE_CHECKING, VT100, WINDOWS from .__version__ import S_BUILD_DT, S_VERSION from .stolen import surrogateescape @@ -95,6 +95,8 @@ if TYPE_CHECKING: from .authsrv import VFS +FAKE_MP = False + try: import multiprocessing as mp @@ -429,6 +431,52 @@ class HLog(logging.Handler): self.log_func(record.name[-21:], msg, c) +class NetMap(object): + def __init__(self, ips: list[str], netdevs: dict[str, str]) -> None: + if "::" in ips: + ips = [x for x in ips if x != "::"] + list( + [x.split("/")[0] for x in netdevs if ":" in x] + ) + ips.append("0.0.0.0") + + if "0.0.0.0" in ips: + ips = [x for x in ips if x != "0.0.0.0"] + list( + [x.split("/")[0] for x in netdevs if ":" not in x] + ) + + ips = [x for x in ips if x not in ("::1", "127.0.0.1")] + ips = [[x for x in netdevs if x.startswith(y + "/")][0] for y in ips] + + self.cache: dict[str, str] = {} + self.b2sip: dict[bytes, str] = {} + self.b2net: dict[bytes, Union[IPv4Network, IPv6Network]] = {} + self.bip: list[bytes] = [] + for ip in ips: + v6 = ":" in ip + fam = socket.AF_INET6 if v6 else socket.AF_INET + bip = socket.inet_pton(fam, ip.split("/")[0]) + self.bip.append(bip) + self.b2sip[bip] = ip.split('/')[0] + self.b2net[bip] = (IPv6Network if v6 else IPv4Network)(ip, False) + + self.bip.sort(reverse=True) + + def map(self, ip: str) -> str: + try: + return self.cache[ip] + except: + pass + + v6 = ":" in ip + ci = IPv6Address(ip) if v6 else IPv4Address(ip) + bip = next((x for x in self.bip if ci in self.b2net[x]), None) + ret = self.b2sip[bip] if bip else "" + if len(self.cache) > 9000: + self.cache = {} + self.cache[ip] = ret + return ret + + class UnrecvEOF(OSError): pass diff --git a/copyparty/web/a/up2k.py b/copyparty/web/a/up2k.py new file mode 120000 index 00000000..90ad442e --- /dev/null +++ b/copyparty/web/a/up2k.py @@ -0,0 +1 @@ +../../../bin/up2k.py \ No newline at end of file diff --git a/copyparty/web/a/webdav-cfg.bat b/copyparty/web/a/webdav-cfg.bat new file mode 120000 index 00000000..63c3b9c6 --- /dev/null +++ b/copyparty/web/a/webdav-cfg.bat @@ -0,0 +1 @@ +../../../contrib/webdav-cfg.bat \ No newline at end of file diff --git a/copyparty/web/splash.css b/copyparty/web/splash.css index 9794bf4d..bbd557c2 100644 --- a/copyparty/web/splash.css +++ b/copyparty/web/splash.css @@ -10,6 +10,9 @@ html { padding: 0 1em 3em 1em; line-height: 1.3em; } +#wrap.w { + max-width: 96%; +} h1 { border-bottom: 1px solid #ccc; margin: 2em 0 .4em 0; @@ -25,15 +28,16 @@ a { text-decoration: none; border-bottom: 1px solid #8ab; border-radius: .2em; - padding: .2em .8em; + padding: .2em .6em; + margin: 0 .3em; } -a+a { - margin-left: .5em; +td a { + margin: 0; } -.refresh, +.af, .logout { float: right; - margin: -.2em 0 0 .5em; + margin: -.2em 0 0 .8em; } .logout, a.r { @@ -64,9 +68,15 @@ table { .num td:first-child { text-align: right; } +.c { + text-align: center; +} .btns { margin: 1em 0; } +.btns>a:first-child { + margin-left: 0; +} #msg { margin: 3em 0; } @@ -83,6 +93,41 @@ blockquote { border-left: .3em solid rgba(128,128,128,0.5); border-radius: 0 0 0 .25em; } +pre, code { + color: #480; + background: #fff; + font-family: 'scp', monospace, monospace; + border: 1px solid rgba(128,128,128,0.3); + border-radius: .2em; + padding: .15em .2em; +} +html.z pre, +html.z code { + color: #9e0; + background: rgba(0,16,0,0.2); +} +.ossel a.r { + box-shadow: 0 .3em 1em #c04; +} +.os { + line-height: 1.5em; +} +.sph { + margin-top: 4em; +} +.sph code { + margin-left: .3em; +} +pre b, +code b { + color: #000; + font-weight: normal; + text-shadow: 0 0 .2em #0f0; +} +html.z pre b, +html.z code b { + color: #fff; +} html.z { @@ -102,6 +147,9 @@ html.z a.r { background: #804; border-color: #c28; } +html.z .ossel a.r { + box-shadow: 0 .3em 1em #704, 0 .3em 1em #704; +} html.z input { color: #fff; background: #626; diff --git a/copyparty/web/splash.html b/copyparty/web/splash.html index b003b454..67205b2c 100644 --- a/copyparty/web/splash.html +++ b/copyparty/web/splash.html @@ -13,7 +13,8 @@
- refresh + refresh + connect {%- if this.uname == '*' %}

howdy stranger   (you're not logged in)

diff --git a/copyparty/web/splash.js b/copyparty/web/splash.js index fad3292c..7bb9037c 100644 --- a/copyparty/web/splash.js +++ b/copyparty/web/splash.js @@ -24,11 +24,14 @@ var Ls = { ".s1": "kartlegg", "t1": "handling", "u2": "tid siden noen sist skrev til serveren$N( opplastning / navneendring / ... )$N$N17d = 17 dager$N1h23 = 1 time 23 minutter$N4m56 = 4 minuter 56 sekunder", + "v1": "koble til", + "v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!" }, "eng": { "d2": "shows the state of all active threads", "e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes", "u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds", + "v2": "use this server as a local HDD$N$NWARNING: this will show your password!", } }, d = Ls[sread("lang") || lang]; diff --git a/copyparty/web/svcs.html b/copyparty/web/svcs.html new file mode 100644 index 00000000..02ec73bc --- /dev/null +++ b/copyparty/web/svcs.html @@ -0,0 +1,199 @@ + + + + + + {{ args.doctitle }} @ {{ args.name }} + + +{{ html_head }} + + + + + +
+
+

browse files // control panel

+

or choose your OS for cooler alternatives:

+
+ Windows + Linux + macOS +
+
+ +

+ make this server appear on your computer as a regular HDD!
+ pick your favorite below (sorted by performance, best first) and lets 🎉
+
+ placeholders: + + {% if accs %}{{ pw }}=password, {% endif %}W:=mountpoint + + + {% if accs %}{{ pw }}=password, {% endif %}mp=mountpoint + +

+ + + + {% if not args.no_dav %} +

WebDAV

+ +
+

note: rclone-FTP is a bit faster, so {% if args.ftp or args.ftps %}try that first{% else %}consider enabling FTP in server settings{% endif %}

+

if you can, install winfsp+rclone and then paste this in cmd:

+
+                rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=other{% if accs %} user=k pass={{ pw }}{% endif %}
+                rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ vp }} W:
+            
+ {% if s %} +

note: if you are on LAN (or just dont have valid certificates), add --no-check-certificate to the mount command
---

+ {% endif %} + +

if you want to use the native WebDAV client in windows instead (slow and buggy), first run webdav-cfg.bat to remove the 47 MiB filesize limit (also fixes password login), then connect:

+
+                net use w: http{{ s }}://{{ ep }}/{{ vp }}{% if accs %} k /user:{{ pw }}{% endif %}
+            
+
+ +
+
+                yum install davfs2
+                {% if accs %}printf '%s\n' {{ pw }} k | {% endif %}mount -t davfs -ouid=1000 http{{ s }}://{{ ep }}/{{ vp }} mp
+            
+

or you can use rclone instead, which is much slower but doesn't require root:

+
+                rclone config create {{ aname }}-dav webdav url=http{{ s }}://{{ rip }}{{ hport }} vendor=other{% if accs %} user=k pass={{ pw }}{% endif %}
+                rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-dav:{{ vp }} mp
+            
+ {% if s %} +

note: if you are on LAN (or just dont have valid certificates), add --no-check-certificate to the mount command
---

+ {% endif %} + +

or the emergency alternative (gnome/gui-only):

+ +
+                {%- if accs %}
+                echo {{ pw }} | gio mount dav{{ s }}://k@{{ ep }}/{{ vp }}
+                {%- else %}
+                gio mount -a dav{{ s }}://{{ ep }}/{{ vp }}
+                {%- endif %}
+            
+
+ +
+
+                osascript -e ' mount volume "http{{ s }}://k:{{ pw }}@{{ ep }}/{{ vp }}" '
+            
+

or you can open up a Finder, press command-K and paste this instead:

+
+                http{{ s }}://k:{{ pw }}@{{ ep }}/{{ vp }}
+            
+ + {% if s %} +

replace https with http if it doesn't work

+ {% endif %} +
+ {% endif %} + + + + {% if args.ftp or args.ftps %} +

FTP

+ +
+

if you can, install winfsp+rclone and then paste this in cmd:

+
+                rclone config create {{ aname }}-ftp ftp host={{ rip }} port={{ args.ftp or args.ftps }} pass=k user={% if accs %}{{ pw }}{% else %}anonymous{% endif %} tls={{ "false" if args.ftp else "true" }}
+                rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftp:{{ vp }} W:
+            
+

if you want to use the native FTP client in windows instead (please dont), press win+R and run this command:

+
+                explorer {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}{{ pw }}:k@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ vp }}
+            
+
+ +
+
+                rclone config create {{ aname }}-ftp ftp host={{ rip }} port={{ args.ftp or args.ftps }} pass=k user={% if accs %}{{ pw }}{% else %}anonymous{% endif %} tls={{ "false" if args.ftp else "true" }}
+                rclone mount --vfs-cache-mode writes --dir-cache-time 5s {{ aname }}-ftp:{{ vp }} mp
+            
+

emergency alternative (gnome/gui-only):

+ +
+                {%- if accs %}
+                echo {{ pw }} | gio mount ftp{{ "" if args.ftp else "s" }}://k@{{ host }}:{{ args.ftp or args.ftps }}/{{ vp }}
+                {%- else %}
+                gio mount -a ftp{{ "" if args.ftp else "s" }}://{{ host }}:{{ args.ftp or args.ftps }}/{{ vp }}
+                {%- endif %}
+            
+
+ +
+

note: FTP is read-only on macos; please use WebDAV instead

+
+                open {{ "ftp" if args.ftp else "ftps" }}://{% if accs %}k:{{ pw }}@{% else %}anonymous:@{% endif %}{{ host }}:{{ args.ftp or args.ftps }}/{{ vp }}
+            
+
+ {% endif %} + + + +

partyfuse

+

+ partyfuse.py -- fast, read-only, + needs winfsp + doesn't need root +

+
+            partyfuse.py{% if accs %} -a {{ pw }}{% endif %} http{{ s }}://{{ ep }}/{{ vp }} W:mp
+        
+ {% if s %} +

note: if you are on LAN (or just dont have valid certificates), add -td

+ {% endif %} +

+ you can use up2k.py to upload (sometimes faster than web-browsers) +

+ + + {% if args.smb %} +

SMB / CIFS

+ bug: max ~300 files in each folder + +
+
+                net use w: \\{{ host }}\a{% if accs %} k /user:{{ pw }}{% endif %}
+            
+ +
+ +
+
+                mount -t cifs -o{% if accs %}user={{ pw }},pass=k,{% endif %}vers={{ 1 if args.smb1 else 2 }}.0,port={{ args.smb_port }},uid=1000 //{{ host }}/a/ mp
+            
+ +
+ +
+            @@@ net use w: \\{{ host }}\a k /user:{{ pw }}
+        
+ {% endif %} + + + +
+ π + + + + + diff --git a/copyparty/web/svcs.js b/copyparty/web/svcs.js new file mode 100644 index 00000000..28b05b5d --- /dev/null +++ b/copyparty/web/svcs.js @@ -0,0 +1,34 @@ +var oa = QSA('pre'); +for (var a = 0; a < oa.length; a++) { + var html = oa[a].innerHTML, + nd = /^ +/.exec(html)[0].length, + rd = new RegExp('(^|\n) {' + nd + '}', 'g'); + + oa[a].innerHTML = html.replace(rd, '$1').replace(/[ \r\n]+$/, ''); +} + + +oa = QSA('.ossel a'); +for (var a = 0; a < oa.length; a++) + oa[a].onclick = esetos; + +function esetos(e) { + ev(e); + setos(e.target.id.slice(1)); +} + +function setos(os) { + var oa = QSA('.os'); + for (var a = 0; a < oa.length; a++) + oa[a].style.display = 'none'; + + var oa = QSA('.' + os); + for (var a = 0; a < oa.length; a++) + oa[a].style.display = ''; + + oa = QSA('.ossel a'); + for (var a = 0; a < oa.length; a++) + clmod(oa[a], 'r', oa[a].id.slice(1) == os); +} + +setos(WINDOWS ? 'win' : LINUX ? 'lin' : MACOS ? 'mac' : ''); diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 4106c9c8..f6240a5b 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -17,7 +17,9 @@ var wah = '', VCHROME = CHROME ? 1 : 0, FIREFOX = ('netscape' in window) && / rv:/.test(navigator.userAgent), IPHONE = TOUCH && /iPhone|iPad|iPod/i.test(navigator.userAgent), - WINDOWS = navigator.platform ? navigator.platform == 'Win32' : /Windows/.test(navigator.userAgent); + LINUX = /Linux/.test(navigator.userAgent), + MACOS = /[^a-z]mac ?os/i.test(navigator.userAgent), + WINDOWS = /Windows/.test(navigator.userAgent); if (!window.WebAssembly || !WebAssembly.Memory) window.WebAssembly = false; diff --git a/scripts/make-pypi-release.sh b/scripts/make-pypi-release.sh index 24876246..233b3589 100755 --- a/scripts/make-pypi-release.sh +++ b/scripts/make-pypi-release.sh @@ -92,7 +92,11 @@ rm -rf build/pypi mkdir -p build/pypi cp -pR setup.py README.md LICENSE copyparty tests bin scripts/strip_hints build/pypi/ cd build/pypi -tar --strip-components=2 -xf ../strip-hints-0.1.10.tar.gz strip-hints-0.1.10/src/strip_hints +f=../strip-hints-0.1.10.tar.gz +[ -e $f ] || + (url=https://files.pythonhosted.org/packages/9c/d4/312ddce71ee10f7e0ab762afc027e07a918f1c0e1be5b0069db5b0e7542d/strip-hints-0.1.10.tar.gz; + wget -O$f "$url" || curl -L "$url" >$f) +tar --strip-components=2 -xf $f strip-hints-0.1.10/src/strip_hints python3 -c 'from strip_hints.a import uh; uh("copyparty")' ./setup.py clean2 diff --git a/scripts/make-sfx.sh b/scripts/make-sfx.sh index f771f9b6..807725a1 100755 --- a/scripts/make-sfx.sh +++ b/scripts/make-sfx.sh @@ -245,6 +245,21 @@ tmpdir="$( } rm -f ../tar + # resolve symlinks + find -type l | + while IFS= read -r f1; do ( + cd "${f1%/*}" + f1="./${f1##*/}" + f2="$(readlink "$f1")" + [ -e "$f2" ] || f2="../$f2" + [ -e "$f2" ] || { + echo could not resolve "$f1" + exit 1 + } + rm "$f1" + cp -pv "$f2" "$f1" + ); done + # insert asynchat mkdir copyparty/vend for n in asyncore.py asynchat.py; do diff --git a/scripts/sfx.ls b/scripts/sfx.ls index c43d128c..e2c546f2 100644 --- a/scripts/sfx.ls +++ b/scripts/sfx.ls @@ -25,6 +25,7 @@ copyparty/res, copyparty/res/COPYING.txt, copyparty/res/insecure.pem, copyparty/smbd.py, +copyparty/ssdp.py, copyparty/star.py, copyparty/stolen, copyparty/stolen/__init__.py, @@ -57,6 +58,9 @@ copyparty/vend, copyparty/vend/asynchat.py, copyparty/vend/asyncore.py, copyparty/web, +copyparty/web/a, +copyparty/web/a/up2k.py, +copyparty/web/a/webdav-cfg.bat, copyparty/web/baguettebox.js, copyparty/web/browser.css, copyparty/web/browser.html, @@ -94,6 +98,8 @@ copyparty/web/msg.html, copyparty/web/splash.css, copyparty/web/splash.html, copyparty/web/splash.js, +copyparty/web/svcs.html, +copyparty/web/svcs.js, copyparty/web/ui.css, copyparty/web/up2k.js, copyparty/web/util.js,