Help for vbox.view_downloads

Sample CLI

gway vbox view-downloads

References

Full Code

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