From a4b56c74c7b15e1ed7c635c980b21f8d5992e499 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 10 Feb 2023 18:37:37 +0000 Subject: [PATCH] support long filepaths on win7 + misc windows fixes --- README.md | 2 +- bin/mtag/image-noexif.py | 2 +- copyparty/__main__.py | 5 +++ copyparty/authsrv.py | 8 ++-- copyparty/ftpd.py | 18 +++++++-- copyparty/mtag.py | 30 ++++++++++++--- copyparty/smbd.py | 8 +++- copyparty/svchub.py | 5 ++- copyparty/th_srv.py | 7 ++-- copyparty/up2k.py | 71 ++++++++++++++++++------------------ copyparty/util.py | 67 ++++++++++++++++++++++++++++------ scripts/pyinstaller/build.sh | 38 ++++++++++++------- tests/util.py | 2 +- 13 files changed, 179 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 2919fb3a..89da051e 100644 --- a/README.md +++ b/README.md @@ -1350,7 +1350,7 @@ enable [thumbnails](#thumbnails) of... * **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin` * **JPEG XL pictures:** `pyvips` or `ffmpeg` -enable [smb](#smb-server) support: +enable [smb](#smb-server) support (**not** recommended): * `impacket==0.10.0` `pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips` diff --git a/bin/mtag/image-noexif.py b/bin/mtag/image-noexif.py index 0b0d5918..d822c5ec 100644 --- a/bin/mtag/image-noexif.py +++ b/bin/mtag/image-noexif.py @@ -61,7 +61,7 @@ def main(): os.chdir(cwd) f1 = fsenc(fn) - f2 = os.path.join(b"noexif", f1) + f2 = fsenc(os.path.join(b"noexif", fn)) cmd = [ b"exiftool", b"-exif:all=", diff --git a/copyparty/__main__.py b/copyparty/__main__.py index c70fc6db..b30308a7 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -36,8 +36,10 @@ from .util import ( UNPLICATIONS, align_tab, ansi_re, + is_exe, min_ex, py_desc, + pybin, termsize, wrap, ) @@ -1065,6 +1067,9 @@ def main(argv: Optional[list[str]] = None) -> None: showlic() sys.exit(0) + if is_exe: + print("pybin: {}\n".format(pybin), end="") + ensure_locale() if HAVE_SSL: ensure_cert() diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index cb868f4b..4102e2e4 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -14,7 +14,7 @@ from datetime import datetime from .__init__ import ANYWIN, TYPE_CHECKING, WINDOWS from .bos import bos -from .cfg import vf_bmap, vf_vmap, vf_cmap, onedash, flagdescs, permdescs +from .cfg import vf_bmap, vf_vmap, vf_cmap, flagdescs, permdescs from .util import ( IMPLICATIONS, META_NOBOTS, @@ -22,7 +22,7 @@ from .util import ( UNPLICATIONS, Pebkac, absreal, - fsenc, + afsenc, get_df, humansize, relchk, @@ -1083,7 +1083,7 @@ class AuthSrv(object): promote = [] demote = [] for vol in vfs.all_vols.values(): - zb = hashlib.sha512(fsenc(vol.realpath)).digest() + zb = hashlib.sha512(afsenc(vol.realpath)).digest() hid = base64.b32encode(zb).decode("ascii").lower() vflag = vol.flags.get("hist") if vflag == "-": @@ -1102,7 +1102,7 @@ class AuthSrv(object): except: owner = None - me = fsenc(vol.realpath).rstrip() + me = afsenc(vol.realpath).rstrip() if owner not in [None, me]: continue diff --git a/copyparty/ftpd.py b/copyparty/ftpd.py index 8adf6b40..f2abdf1c 100644 --- a/copyparty/ftpd.py +++ b/copyparty/ftpd.py @@ -21,6 +21,7 @@ from .util import ( exclude_dotfiles, fsenc, ipnorm, + pybin, relchk, sanitize_fn, vjoin, @@ -135,7 +136,8 @@ class FtpFs(AbstractedFS): try: vpath = vpath.replace("\\", "/").lstrip("/") rd, fn = os.path.split(vpath) - if ANYWIN and not relchk(rd): + if ANYWIN and relchk(rd): + logging.warning("malicious vpath: %s", vpath) raise FilesystemError("unsupported characters in filepath") fn = sanitize_fn(fn or "", "", [".prologue.html", ".epilogue.html"]) @@ -417,7 +419,7 @@ class Ftpd(object): h1 = SftpHandler except: t = "\nftps requires pyopenssl;\nplease run the following:\n\n {} -m pip install --user pyopenssl\n" - print(t.format(sys.executable)) + print(t.format(pybin)) sys.exit(1) h1.certfile = os.path.join(self.args.E.cfg, "cert.pem") @@ -450,10 +452,18 @@ class Ftpd(object): lgr = logging.getLogger("pyftpdlib") lgr.setLevel(logging.DEBUG if self.args.ftpv else logging.INFO) + ips = self.args.i + if "::" in ips: + ips.append("0.0.0.0") + ioloop = IOLoop() - for ip in self.args.i: + for ip in ips: for h, lp in hs: - FTPServer((ip, int(lp)), h, ioloop) + try: + FTPServer((ip, int(lp)), h, ioloop) + except: + if ip != "0.0.0.0" or "::" not in ips: + raise Daemon(ioloop.loop, "ftp") diff --git a/copyparty/mtag.py b/copyparty/mtag.py index 62312dde..9f48c3c6 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -10,7 +10,17 @@ import sys from .__init__ import PY2, WINDOWS, E, unicode from .bos import bos -from .util import REKOBO_LKEY, fsenc, min_ex, retchk, runcmd, uncyg +from .util import ( + REKOBO_LKEY, + sfsenc, + fsenc, + is_exe, + min_ex, + pybin, + retchk, + runcmd, + uncyg, +) if True: # pylint: disable=using-constant-test from typing import Any, Union @@ -285,9 +295,14 @@ class MTag(object): self.log(msg, c=3) if not self.usable: + if is_exe: + t = "need ffmpeg to read media tags; copyparty.exe cannot use mutagen" + self.log(t) + return + msg = "need Mutagen{} to read media tags so please run this:\n{}{} -m pip install --user mutagen\n" - pybin = os.path.basename(sys.executable) - self.log(msg.format(or_ffprobe, " " * 37, pybin), c=1) + pyname = os.path.basename(pybin) + self.log(msg.format(or_ffprobe, " " * 37, pyname), c=1) return # https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html @@ -519,12 +534,15 @@ class MTag(object): env = os.environ.copy() try: + if is_exe: + raise Exception() + pypath = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) zsl = [str(pypath)] + [str(x) for x in sys.path if x] pypath = str(os.pathsep.join(zsl)) env["PYTHONPATH"] = pypath except: - if not E.ox: + if not E.ox and not is_exe: raise ret: dict[str, Any] = {} @@ -532,7 +550,7 @@ class MTag(object): try: cmd = [parser.bin, abspath] if parser.bin.endswith(".py"): - cmd = [sys.executable] + cmd + cmd = [pybin] + cmd args = { "env": env, @@ -551,7 +569,7 @@ class MTag(object): else: cmd = ["nice"] + cmd - bcmd = [fsenc(x) for x in cmd] + bcmd = [sfsenc(x) for x in cmd[:-1]] + [fsenc(cmd[-1])] rc, v, err = runcmd(bcmd, **args) # type: ignore retchk(rc, bcmd, err, self.log, 5, self.args.mtag_v) v = v.strip() diff --git a/copyparty/smbd.py b/copyparty/smbd.py index a08bedf6..bad6e540 100644 --- a/copyparty/smbd.py +++ b/copyparty/smbd.py @@ -12,7 +12,7 @@ from types import SimpleNamespace from .__init__ import ANYWIN, TYPE_CHECKING from .authsrv import LEELOO_DALLAS, VFS from .bos import bos -from .util import Daemon, min_ex +from .util import Daemon, is_exe, min_ex, pybin if True: # pylint: disable=using-constant-test from typing import Any @@ -42,8 +42,12 @@ class SMB(object): from impacket import smbserver from impacket.ntlm import compute_lmhash, compute_nthash except ImportError: + if is_exe: + print("copyparty.exe cannot do SMB") + sys.exit(1) + m = "\033[36m\n{}\033[31m\n\nERROR: need 'impacket'; please run this command:\033[33m\n {} -m pip install --user impacket\n\033[0m" - print(m.format(min_ex(), sys.executable)) + print(m.format(min_ex(), pybin)) sys.exit(1) # patch vfs into smbserver.os diff --git a/copyparty/svchub.py b/copyparty/svchub.py index c28b7910..e6d27802 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -44,6 +44,7 @@ from .util import ( ansi_re, min_ex, mp, + pybin, start_log_thrs, start_stackmon, ) @@ -206,7 +207,7 @@ class SvcHub(object): self.thumbsrv = ThumbSrv(self) else: msg = "need either Pillow, pyvips, or FFmpeg to create thumbnails; for example:\n{0}{1} -m pip install --user Pillow\n{0}{1} -m pip install --user pyvips\n{0}apt install ffmpeg" - msg = msg.format(" " * 37, os.path.basename(sys.executable)) + msg = msg.format(" " * 37, os.path.basename(pybin)) self.log("thumb", msg, c=3) if not args.no_acode and args.no_thumb: @@ -413,7 +414,7 @@ class SvcHub(object): lh = codecs.open(fn, "w", encoding="utf-8", errors="replace") - argv = [sys.executable] + self.argv + argv = [pybin] + self.argv if hasattr(shlex, "quote"): argv = [shlex.quote(x) for x in argv] else: diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index 000d781f..4e906185 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -20,6 +20,7 @@ from .util import ( Cooldown, Daemon, Pebkac, + afsenc, fsenc, min_ex, runcmd, @@ -82,14 +83,14 @@ def thumb_path(histpath: str, rem: str, mtime: float, fmt: str) -> str: # base64 = 64 = 4096 rd, fn = vsplit(rem) if rd: - h = hashlib.sha512(fsenc(rd)).digest() + h = hashlib.sha512(afsenc(rd)).digest() b64 = base64.urlsafe_b64encode(h).decode("ascii")[:24] rd = "{}/{}/".format(b64[:2], b64[2:4]).lower() + b64 else: rd = "top" # could keep original filenames but this is safer re pathlen - h = hashlib.sha512(fsenc(fn)).digest() + h = hashlib.sha512(afsenc(fn)).digest() fn = base64.urlsafe_b64encode(h).decode("ascii")[:24] if fmt in ("opus", "caf"): @@ -201,7 +202,7 @@ class ThumbSrv(object): inf_path = os.path.join(thdir, "dir.txt") if not bos.path.exists(inf_path): with open(inf_path, "wb") as f: - f.write(fsenc(os.path.dirname(abspath))) + f.write(afsenc(os.path.dirname(abspath))) self.busy[tpath] = [cond] do_conv = True diff --git a/copyparty/up2k.py b/copyparty/up2k.py index a511fa8b..38e74c38 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -37,6 +37,7 @@ from .util import ( atomic_move, db_ex_chk, djoin, + sfsenc, fsenc, gen_filekey, gen_filekey_dbg, @@ -395,7 +396,7 @@ class Up2k(object): def _vis_job_progress(self, job: dict[str, Any]) -> str: perc = 100 - (len(job["need"]) * 100.0 / len(job["hash"])) - path = os.path.join(job["ptop"], job["prel"], job["name"]) + path = djoin(job["ptop"], job["prel"], job["name"]) return "{:5.1f}% {}".format(perc, path) def _vis_reg_progress(self, reg: dict[str, dict[str, Any]]) -> list[str]: @@ -691,7 +692,7 @@ class Up2k(object): pass for k, job in reg2.items(): - path = os.path.join(job["ptop"], job["prel"], job["name"]) + path = djoin(job["ptop"], job["prel"], job["name"]) if bos.path.exists(path): reg[k] = job job["poke"] = time.time() @@ -1086,7 +1087,7 @@ class Up2k(object): else: rd = drd - abspath = os.path.join(top, rd) + abspath = djoin(top, rd) self.pp.msg = "b{} {}".format(ndirs - nchecked, abspath) try: if os.path.isdir(abspath): @@ -1127,7 +1128,7 @@ class Up2k(object): if crd != rd: crd = rd try: - cdc = set(os.listdir(os.path.join(top, rd))) + cdc = set(os.listdir(djoin(top, rd))) except: cdc.clear() @@ -1203,7 +1204,7 @@ class Up2k(object): rd = drd fn = dfn - abspath = os.path.join(ptop, rd, fn) + abspath = djoin(ptop, rd, fn) if rei and rei.search(abspath): continue @@ -1412,7 +1413,7 @@ class Up2k(object): q = "insert into mt values (?,'t:mtp','a')" cur.execute(q, (w[:16],)) - abspath = os.path.join(ptop, rd, fn) + abspath = djoin(ptop, rd, fn) self.pp.msg = "c{} {}".format(nq, abspath) if not mpool: n_tags = self._tagscan_file(cur, entags, w, abspath, ip, at) @@ -1576,7 +1577,7 @@ class Up2k(object): q = "select rd, fn, ip, at from up where substr(w,1,16)=? limit 1" rd, fn, ip, at = cur.execute(q, (w,)).fetchone() rd, fn = s3dec(rd, fn) - abspath = os.path.join(ptop, rd, fn) + abspath = djoin(ptop, rd, fn) q = "select k from mt where w = ?" zq = cur.execute(q, (w,)).fetchall() @@ -2053,7 +2054,7 @@ class Up2k(object): if dp_dir.startswith("//") or dp_fn.startswith("//"): dp_dir, dp_fn = s3dec(dp_dir, dp_fn) - dp_abs = "/".join([ptop, dp_dir, dp_fn]) + dp_abs = djoin(ptop, dp_dir, dp_fn) try: st = bos.stat(dp_abs) if stat.S_ISLNK(st.st_mode): @@ -2128,7 +2129,7 @@ class Up2k(object): # ensure the files haven't been deleted manually names = [job[x] for x in ["name", "tnam"] if x in job] for fn in names: - path = os.path.join(job["ptop"], job["prel"], fn) + path = djoin(job["ptop"], job["prel"], fn) try: if bos.path.getsize(path) > 0: # upload completed or both present @@ -2140,9 +2141,9 @@ class Up2k(object): break else: # file contents match, but not the path - src = os.path.join(job["ptop"], job["prel"], job["name"]) - dst = os.path.join(cj["ptop"], cj["prel"], cj["name"]) - vsrc = os.path.join(job["vtop"], job["prel"], job["name"]) + src = djoin(job["ptop"], job["prel"], job["name"]) + dst = djoin(cj["ptop"], cj["prel"], cj["name"]) + vsrc = djoin(job["vtop"], job["prel"], job["name"]) vsrc = vsrc.replace("\\", "/") # just for prints anyways if job["need"]: self.log("unfinished:\n {0}\n {1}".format(src, dst)) @@ -2180,7 +2181,7 @@ class Up2k(object): else: job["name"] = self._untaken(pdir, cj, now) - dst = os.path.join(job["ptop"], job["prel"], job["name"]) + dst = djoin(job["ptop"], job["prel"], job["name"]) if not self.args.nw: try: dvf = self.flags[job["ptop"]] @@ -2270,7 +2271,7 @@ class Up2k(object): and "fk" in vfs.flags and (cj["user"] in vfs.axs.uread or cj["user"] in vfs.axs.upget) ): - ap = absreal(os.path.join(job["ptop"], job["prel"], job["name"])) + ap = absreal(djoin(job["ptop"], job["prel"], job["name"])) ino = 0 if ANYWIN else bos.stat(ap).st_ino fk = self.gen_fk(self.args.fk_salt, ap, job["size"], ino) ret["fk"] = fk[: vfs.flags["fk"]] @@ -2284,7 +2285,7 @@ class Up2k(object): if self.args.nw: return fname - fp = os.path.join(fdir, fname) + fp = djoin(fdir, fname) if job.get("replace") and bos.path.exists(fp): self.log("replacing existing file at {}".format(fp)) bos.unlink(fp) @@ -2397,7 +2398,7 @@ class Up2k(object): t = "that chunk is already being written to:\n {}\n {} {}/{}\n {}" raise Pebkac(400, t.format(wark, chash, idx, nh, job["name"])) - path = os.path.join(job["ptop"], job["prel"], job["tnam"]) + path = djoin(job["ptop"], job["prel"], job["tnam"]) chunksize = up2k_chunksize(job["size"]) ofs = [chunksize * x for x in nchunk] @@ -2428,9 +2429,9 @@ class Up2k(object): self.db_act = time.time() try: job = self.registry[ptop][wark] - pdir = os.path.join(job["ptop"], job["prel"]) - src = os.path.join(pdir, job["tnam"]) - dst = os.path.join(pdir, job["name"]) + pdir = djoin(job["ptop"], job["prel"]) + src = djoin(pdir, job["tnam"]) + dst = djoin(pdir, job["name"]) except Exception as ex: return "confirm_chunk, wark, " + repr(ex) # type: ignore @@ -2463,9 +2464,9 @@ class Up2k(object): self.db_act = time.time() try: job = self.registry[ptop][wark] - pdir = os.path.join(job["ptop"], job["prel"]) - src = os.path.join(pdir, job["tnam"]) - dst = os.path.join(pdir, job["name"]) + pdir = djoin(job["ptop"], job["prel"]) + src = djoin(pdir, job["tnam"]) + dst = djoin(pdir, job["name"]) except Exception as ex: raise Pebkac(500, "finish_upload, wark, " + repr(ex)) @@ -2532,7 +2533,7 @@ class Up2k(object): cur = self.cur.get(ptop) for rd, fn, lmod in dupes: - d2 = os.path.join(ptop, rd, fn) + d2 = djoin(ptop, rd, fn) if os.path.exists(d2): continue @@ -2710,7 +2711,7 @@ class Up2k(object): break n_files += 1 - abspath = os.path.join(adir, fn) + abspath = djoin(adir, fn) volpath = "{}/{}".format(vrem, fn).strip("/") vpath = "{}/{}".format(dbv.vpath, volpath).strip("/") self.log("rm {}\n {}".format(vpath, abspath)) @@ -2989,14 +2990,14 @@ class Up2k(object): or to first remaining full if no dabs (delete) """ dupes = [] - sabs = os.path.join(sptop, srem) + sabs = djoin(sptop, srem) q = "select rd, fn from up where substr(w,1,16)=? and w=?" for ptop, cur in self.cur.items(): for rd, fn in cur.execute(q, (wark[:16], wark)): if rd.startswith("//") or fn.startswith("//"): rd, fn = s3dec(rd, fn) - dvrem = "/".join([rd, fn]).strip("/") + dvrem = vjoin(rd, fn).strip("/") if ptop != sptop or srem != dvrem: dupes.append([ptop, dvrem]) self.log("found {} dupe: [{}] {}".format(wark, ptop, dvrem)) @@ -3007,7 +3008,7 @@ class Up2k(object): full: dict[str, tuple[str, str]] = {} links: dict[str, tuple[str, str]] = {} for ptop, vp in dupes: - ap = os.path.join(ptop, vp) + ap = djoin(ptop, vp) try: d = links if bos.path.islink(ap) else full d[ap] = (ptop, vp) @@ -3111,7 +3112,7 @@ class Up2k(object): def _new_upload(self, job: dict[str, Any]) -> None: pdir = djoin(job["ptop"], job["prel"]) - if not job["size"] and bos.path.isfile(os.path.join(pdir, job["name"])): + if not job["size"] and bos.path.isfile(djoin(pdir, job["name"])): return self.registry[job["ptop"]][job["wark"]] = job @@ -3156,7 +3157,7 @@ class Up2k(object): suffix = "-{:.6f}-{}".format(job["t0"], dip) with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as zfw: f, job["tnam"] = zfw["orz"] - abspath = os.path.join(pdir, job["tnam"]) + abspath = djoin(pdir, job["tnam"]) sprs = job["sprs"] sz = job["size"] relabel = False @@ -3255,7 +3256,7 @@ class Up2k(object): x for x in reg.values() if x["need"] - and not bos.path.exists(os.path.join(x["ptop"], x["prel"], x["name"])) + and not bos.path.exists(djoin(x["ptop"], x["prel"], x["name"])) ] if rm or lost: @@ -3268,7 +3269,7 @@ class Up2k(object): del reg[job["wark"]] try: # remove the filename reservation - path = os.path.join(job["ptop"], job["prel"], job["name"]) + path = djoin(job["ptop"], job["prel"], job["name"]) if bos.path.getsize(path) == 0: bos.unlink(path) except: @@ -3277,7 +3278,7 @@ class Up2k(object): try: if len(job["hash"]) == len(job["need"]): # PARTIAL is empty, delete that too - path = os.path.join(job["ptop"], job["prel"], job["tnam"]) + path = djoin(job["ptop"], job["prel"], job["tnam"]) bos.unlink(path) except: pass @@ -3326,7 +3327,7 @@ class Up2k(object): continue # self.log("\n " + repr([ptop, rd, fn])) - abspath = os.path.join(ptop, rd, fn) + abspath = djoin(ptop, rd, fn) try: tags = self.mtag.get(abspath) ntags1 = len(tags) @@ -3376,7 +3377,7 @@ class Up2k(object): if "e2d" not in self.flags[ptop]: continue - abspath = os.path.join(ptop, rd, fn) + abspath = djoin(ptop, rd, fn) self.log("hashing " + abspath) inf = bos.stat(abspath) if not inf.st_size: @@ -3455,6 +3456,6 @@ def up2k_wark_from_hashlist(salt: str, filesize: int, hashes: list[str]) -> str: def up2k_wark_from_metadata(salt: str, sz: int, lastmod: int, rd: str, fn: str) -> str: - ret = fsenc("{}\n{}\n{}\n{}\n{}".format(salt, lastmod, sz, rd, fn)) + ret = sfsenc("{}\n{}\n{}\n{}\n{}".format(salt, lastmod, sz, rd, fn)) ret = base64.urlsafe_b64encode(hashlib.sha512(ret).digest()) return "#{}".format(ret.decode("ascii"))[:44] diff --git a/copyparty/util.py b/copyparty/util.py index e61846f4..7162a6d3 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -14,6 +14,7 @@ import os import platform import re import select +import shutil import signal import socket import stat @@ -290,6 +291,20 @@ REKOBO_KEY = { REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()} +pybin = sys.executable or "" +is_exe = bool(getattr(sys, "frozen", False)) +if is_exe: + pybin = "" + for p in "python3 python".split(): + try: + p = shutil.which(p) + if p: + pybin = p + break + except: + pass + + def py_desc() -> str: interp = platform.python_implementation() py_ver = ".".join([str(x) for x in sys.version_info]) @@ -1704,7 +1719,7 @@ def relchk(rp: str) -> str: def absreal(fpath: str) -> str: try: - return fsdec(os.path.abspath(os.path.realpath(fsenc(fpath)))) + return fsdec(os.path.abspath(os.path.realpath(afsenc(fpath)))) except: if not WINDOWS: raise @@ -1824,6 +1839,24 @@ def _w8enc3(txt: str) -> bytes: return txt.encode(FS_ENCODING, "surrogateescape") +def _msdec(txt: bytes) -> str: + ret = txt.decode(FS_ENCODING, "surrogateescape") + return ret[4:] if ret.startswith("\\\\?\\") else ret + + +def _msaenc(txt: str) -> bytes: + return txt.replace("/", "\\").encode(FS_ENCODING, "surrogateescape") + + +def _msenc(txt: str) -> bytes: + txt = txt.replace("/", "\\") + if ":" not in txt and not txt.startswith("\\\\"): + txt = absreal(txt) + + ret = txt.encode(FS_ENCODING, "surrogateescape") + return ret if ret.startswith(b"\\\\?\\") else b"\\\\?\\" + ret + + w8dec = _w8dec3 if not PY2 else _w8dec2 w8enc = _w8enc3 if not PY2 else _w8enc2 @@ -1838,8 +1871,13 @@ def w8b64enc(txt: str) -> str: return base64.urlsafe_b64encode(w8enc(txt)).decode("ascii") -if not PY2 or not WINDOWS: - fsenc = w8enc +if not PY2 and WINDOWS: + sfsenc = w8enc + afsenc = _msaenc + fsenc = _msenc + fsdec = _msdec +elif not PY2 or not WINDOWS: + fsenc = afsenc = sfsenc = w8enc fsdec = w8dec else: # moonrunes become \x3f with bytestrings, @@ -1850,7 +1888,7 @@ else: def _not_actually_mbcs_dec(txt: bytes) -> str: return txt - fsenc = _not_actually_mbcs_enc + fsenc = afsenc = sfsenc = _not_actually_mbcs_enc fsdec = _not_actually_mbcs_dec @@ -2514,12 +2552,17 @@ def _runhook( log(t.format(arg, ocmd)) env = os.environ.copy() - # try: - pypath = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - zsl = [str(pypath)] + [str(x) for x in sys.path if x] - pypath = str(os.pathsep.join(zsl)) - env["PYTHONPATH"] = pypath - # except: if not E.ox: raise + try: + if is_exe: + raise Exception() + + pypath = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + zsl = [str(pypath)] + [str(x) for x in sys.path if x] + pypath = str(os.pathsep.join(zsl)) + env["PYTHONPATH"] = pypath + except: + if not is_exe: + raise ka = { "env": env, @@ -2545,9 +2588,9 @@ def _runhook( acmd = [cmd, arg] if cmd.endswith(".py"): - acmd = [sys.executable] + acmd + acmd = [pybin] + acmd - bcmd = [fsenc(x) for x in acmd] + bcmd = [fsenc(x) if x == ap else sfsenc(x) for x in acmd] t0 = time.time() if fork: diff --git a/scripts/pyinstaller/build.sh b/scripts/pyinstaller/build.sh index df8cc4d8..8359acc2 100644 --- a/scripts/pyinstaller/build.sh +++ b/scripts/pyinstaller/build.sh @@ -44,21 +44,33 @@ read a b c d _ < <( ) sed -r 's/1,2,3,0/'$a,$b,$c,$d'/;s/1\.2\.3/'$a.$b.$c/ loader.rc2 +excl=( + copyparty.broker_mp + copyparty.broker_mpw + ctypes.macholib + curses + inspect + multiprocessing + pdb + pickle + pyftpdlib.prefork + urllib.request + urllib.response + urllib.robotparser + zipfile +) +false || excl+=( + PIL + PIL.ExifTags + PIL.Image + PIL.ImageDraw + PIL.ImageOps +) +excl=( "${excl[@]/#/--exclude-module }" ) + $APPDATA/python/python37/scripts/pyinstaller \ -y --clean -p mods --upx-dir=. \ - --exclude-module copyparty.broker_mp \ - --exclude-module copyparty.broker_mpw \ - --exclude-module curses \ - --exclude-module ctypes.macholib \ - --exclude-module inspect \ - --exclude-module multiprocessing \ - --exclude-module pdb \ - --exclude-module pickle \ - --exclude-module pyftpdlib.prefork \ - --exclude-module urllib.request \ - --exclude-module urllib.response \ - --exclude-module urllib.robotparser \ - --exclude-module zipfile \ + ${excl[*]} \ --version-file loader.rc2 -i loader.ico -n copyparty -c -F loader.py \ --add-data 'mods/copyparty/res;copyparty/res' \ --add-data 'mods/copyparty/web;copyparty/web' diff --git a/tests/util.py b/tests/util.py index b23fa669..8f02244d 100644 --- a/tests/util.py +++ b/tests/util.py @@ -98,7 +98,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None): ka = {} - ex = "daw dav_inf dav_mac dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod hardlink ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nw xdev xlink xvol" + ex = "daw dav_inf dav_mac dotsrch e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp ed emp force_js getmod hardlink ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_logues no_mv no_readme no_robots no_sb_md no_sb_lg no_scandir no_thumb no_vthumb no_zip nrand nw rand vc xdev xlink xvol" ka.update(**{k: False for k in ex.split()}) ex = "dotpart no_rescan no_sendfile no_voldump plain_ip"