download-as-zip: toplevel optional

This commit is contained in:
ed 2026-05-25 18:32:42 +00:00
parent c28aa08b35
commit cc5420a324
5 changed files with 23 additions and 15 deletions

View file

@ -836,6 +836,7 @@ you can also zip a selection of files or folders by clicking them in the browser
cool trick: download a folder by appending url-params `?tar&opus` or `?tar&mp3` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus/mp3 before they're added to the archive
* super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
* and url-param `&name=foo` changes the name of the toplevel folder in the archive to `foo`, and just `&name` removes the folder entirely
* and url-param `&nodot` skips dotfiles/dotfolders; they are included by default if your account has permission to see them
* and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images (`&p` for audio waveforms)
* can also be used to pregenerate thumbnails; combine with `--th-maxage=9999999` or `--th-clean=0`

View file

@ -900,20 +900,14 @@ class VFS(object):
def zipgen(
self,
vpath: str,
folder: str,
vrem: str,
flt: set[str],
uname: str,
dirs: bool,
dots: int,
scandir: bool,
wrap: bool = True,
) -> Generator[dict[str, Any], None, None]:
# if multiselect: add all items to archive root
# if single folder: the folder itself is the top-level item
folder = "" if flt or not wrap else (vpath.split("/")[-1].lstrip(".") or "top")
g = self.walk(folder, vrem, [], uname, [[True, False]], dots, scandir, False)
for _, _, vpath, apath, files, rd, vd in g:
if flt:

View file

@ -1823,14 +1823,13 @@ class HttpCli(object):
# because lstat=true would not recurse into subfolders
# and this is a rare case where we actually want that
fgen = vn.zipgen(
rem,
"",
rem,
set(),
self.uname,
True,
1,
not self.args.no_scandir,
wrap=False,
)
elif depth == "0":
@ -5163,6 +5162,16 @@ class HttpCli(object):
if items:
fn = "sel-" + fn
if "name" in self.ouparam:
# user-selected name for toplevel folder, or blank for none
vpath = undot(self.ouparam["name"])
elif items:
# multiselect; add all items to archive root
vpath = ""
else:
# single folder; the folder itself is the top-level item
vpath = vpath.split("/")[-1].lstrip(".") or "top"
if vn.flags.get("zipmax") and not (
vn.flags.get("zipmaxu") and self.uname != "*"
):
@ -5209,7 +5218,7 @@ class HttpCli(object):
if cfmt:
self.log("transcoding to [{}]".format(cfmt))
fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt)
fgen = gfilter(fgen, self.thumbcli, self.uname, self.vpath, vpath, cfmt)
now = time.time()
self.dl_id = "%s:%s" % (self.ip, self.addr[1])

View file

@ -700,7 +700,7 @@ class HttpSrv(object):
if not fmts:
continue
log("starting for volume /%s" % (vn.vpath,), 6)
g = vn.walk("x", "/", [], LEELOO_DALLAS, [[True]], 2, scandir, False, False)
g = vn.walk("", "/", [], LEELOO_DALLAS, [[True]], 2, scandir, False, False)
g = gfilter2(g, self, vn.vpath, fmts.split(","))
for f in g:
nfiles += 1

View file

@ -61,6 +61,7 @@ def gfilter(
thumbcli: ThumbCli,
uname: str,
vtop: str,
vname: str,
fmt: str,
) -> Generator[dict[str, Any], None, None]:
from concurrent.futures import ThreadPoolExecutor
@ -70,7 +71,7 @@ def gfilter(
_pools[tp] = 1
try:
for f in fgen:
task = tp.submit(enthumb, thumbcli, uname, vtop, f, fmt)
task = tp.submit(enthumb, thumbcli, uname, vtop, vname, f, fmt)
pend.append((task, f))
if pend[0][0].done() or len(pend) > CORES * 4:
task, f = pend.pop(0)
@ -130,7 +131,7 @@ def gfilter2(
try:
f = {"vp": vp, "st": fi[1]}
task = tp.submit(
enthumb, hsrv.thumbcli, LEELOO_DALLAS, vtop, f, fmt
enthumb, hsrv.thumbcli, LEELOO_DALLAS, vtop, "", f, fmt
)
pend.append((task, f))
if pend[0][0].done() or len(pend) > CORES * 4:
@ -152,14 +153,17 @@ def gfilter2(
def enthumb(
thumbcli: ThumbCli, uname: str, vtop: str, f: dict[str, Any], fmt: str
thumbcli: ThumbCli, uname: str, vtop: str, vname: str, f: dict[str, Any], fmt: str
) -> dict[str, Any]:
rem = f["vp"]
ext = rem.rsplit(".", 1)[-1].lower()
if (fmt == "mp3" and ext == "mp3") or (fmt == "opus" and ext in TAR_NO_OPUS):
raise Exception()
if vname:
vp = vjoin(vtop, rem.split("/", 1)[1])
else:
vp = vjoin(vtop, rem)
vn, rem = thumbcli.asrv.vfs.get(vp, uname, True, False)
dbv, vrem = vn.get_dbv(rem)
thp = thumbcli.get(dbv, vrem, f["st"].st_mtime, fmt)