def view_downloads(*hashes: tuple[str], vbid: str = None, modified_since=None, **kwargs):
"""
GET: Show list of files in the box (with hash), allow selection/downloads.
If a single hash is provided, return that file. Multiple hashes are not supported yet.
- Allows access via full vbid (short.long) or short-only (just the folder name).
- If full vbid is used, shows link to upload more files.
- If modified_since is passed (as iso or epoch seconds), only send file if newer, else 304.
"""
# TODO: Support multiple hashes by checking them one by one. If the first doesn't exist,
# try the next and so forth. Give up when every hash fails to match. First matches is chosen first.
gw.warning(f"Download view: {hashes=} {vbid=} {kwargs=}")
if not vbid:
return render_error("Missing vbid", "You must provide a vbid in the query string.")
# Accept full or short vbid
if "." in vbid:
short, _ = vbid.split(".", 1)
else:
short = vbid # Accept short-only vbid for downloads
folder = gw.resource(*VBOX_PATH, short)
if not os.path.isdir(folder):
return render_error("Box not found", "The folder associated with this vbid does not exist.")
file_map = {} # hash -> full_path
file_info = [] # tuples for UI: (hash, name, size, mtime)
for name in os.listdir(folder):
path = os.path.join(folder, name)
if not os.path.isfile(path):
continue
try:
with open(path, "rb") as f:
data = f.read()
md5 = hashlib.md5(data).hexdigest()
file_map[md5] = path
size = len(data)
mtime = os.path.getmtime(path)
file_info.append((md5, name, size, mtime))
except Exception as e:
gw.error(f"Error reading file {name} in box {vbid}: {e}")
continue
# If a specific file is requested by hash
if hashes:
if len(hashes) > 1:
raise NotImplementedError("Multi-hash downloads are not supported (yet).")
h = hashes[0]
if h not in file_map:
return f"<h1>No matching file</h1><p>Hash {h} not found in this box.</p>"
path = file_map[h]
name = os.path.basename(path)
file_mtime = os.path.getmtime(path)
# Implement modified_since logic
if modified_since:
try:
# Accept both isoformat and epoch seconds
try:
since = float(modified_since)
except ValueError:
since = datetime.fromisoformat(modified_since).timestamp()
if file_mtime <= since:
return HTTPResponse(status=304)
except Exception as e:
gw.error(f"modified_since parse error: {modified_since} ({e})")
return stream_file_response(path, name)
# Render file listing
html = "<h1>Download Files</h1><ul>"
for h, name, size, mtime in file_info:
time_str = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
link = gw.build_url("downloads", h, vbid=vbid)
html += f'<li><a href="{link}">{name}</a> ({size} bytes, modified {time_str}, MD5: {h})</li>'
html += "</ul>"
# Only include upload link if full vbid was used
if "." in vbid:
upload_url = gw.build_url("upload", vbid=vbid)
html += f"<p><a href='{upload_url}'>UPLOAD MORE files to this box</a></p>"
return html