Files
Copyparty-content-tag/DESIGN.md
T
Frog-Lover-Antony 6f9ca3864f v0.1
2026-07-18 17:40:47 +03:00

8.9 KiB

This is a design draft. Lots of things are missing. Fill the holes during planning. Ask user if things are unclear.

Rules

  • Project uses copyparty (github.com/9001/copyparty) for both application, content, user data and interactions trough it's web-dav like interface. Check github.com/9001/copyparty/blob/hovudstraum/docs/devnotes.md for http api reference.
  • Project must be tested with a set of curl/nodejs sanity checks and an automated browser in docker, using gitea actions for releases and local docker for development loops.
  • Project's repository lives at gitea.antonene.space/KORMsoftware/Copyparty-content-tag.
  • Plan development and track progress in PLAN.md.
  • Make tests for each feature (unless bulletproof or not observable from tests e.g. css styles on web-pages, that will require human interaction).
  • All scripts must be served from copyparty. If you add an external library, download a local copy.

Functionality

  • The tagging system is a fully-static html+js application running on copyparty as a backend. It is supposed to make image/video/animation uplolading, tagging and sorting easy.
  • Meets users with a "directory" input field if "current-cwd-header" is not set (basically a login screen). It is a custom url text field that allows setting the working directory. Below is a "check" button and an "enter" button. Check button gets user's permissions for the chosen directory and tells the user whether it is accessible and whether the user has write access.
  • After picking a working directory the user must be dropped into the main ui.
  • App loads database for all files and their tags in tsv format stored under {cwd}/db/latest.tsv.
  • App lists new content in {cwd}/content/unmanaged, checks if files are not already in the loaded database: if not - moves them to {cwd}/content/managed under their new db-related ids, processes hashes and adds them to the database.
  • App computes similarity hashes (phash.js) and main colors for images (1-4) (only images for now).

Copyparty API wrapper (verified against localhost:8086)

All operations use PW: <password> header for auth. Anonymous sees only read-only /public/. Authenticated user sees both read (/public/, no write) and admin (/private/user, full access). Server returns 403 on wrong password.

API endpoints used by the app

Directory resolution ("Check" button)

  • GET /{path}/?ls → JSON with dirs[], files[], perms[] array. If "write"perms, directory is writable.
  • The "Enter" button stores {path} as CWD in localStorage and redirects to main UI.

TSV database read/write

  • Read: GET /{cwd}/db/latest.tsv?txt&pw=... → raw text body. File may not exist on first run (404) — app creates fresh empty DB.
  • Write full: PUT to /private/user/db/latest.tsv?j with Replace: 1 header + binary body → JSON {filesz, fileurl, sha512} on success.
  • Append row: PUT to same URL with ?apnd&j (no Replace needed) appends bytes to existing file.

IMPORTANT: The ?replace URL parameter does NOT work for PUT uploads — only the Replace: 1 HTTP header overwrites. Without it, copyparty deduplicates by appending a timestamp suffix.

File management

  • List dir: GET /{path}/?ls&dots → JSON listing (includes dotfiles).
  • Move file: POST /{src}?move=/{dst} → returns "k" on success. Source gone, dest appears with new name.
  • Copy file: POST /{src}?copy=/{dst} → same pattern as move.
  • Delete single: POST /{path}to?delete&j → JSON confirm.
  • Delete batch: jPOST /?delete&j body ["/abs/path1", "/abs/path2"] at root level.
  • Mkdir: mPOST /{parent}/?replace with act=mkdir&name=<dir> → HTML redirect + "aight" confirm. Auto-creates missing parents.

File upload

  • PUT (binary): PUT /{path}/?j body=file bytes, Replace: 1 header to overwrite.
  • Multipart: mPOST /{dir}/?replace&j with f=@file;filename=name on same URL. Note: the raw curl syntax for multipart requires correct -F formatting — malformed field names cause "expected field" errors.
  • jPOST /?ls body {"q":"raw_field=value"} → plain-text result lines. Syntax from copyparty's search tab (name=, ext=, etc). Returns raw field matches, not a filtered JSON listing. For app use, prefer full-dir ?ls then client-side filtering over server search.

Permission model (confirmed)

  • Root /?ls: unauthenticated → acct: "*", perms: []. Authenticated → acct: "user", both routes visible with route-specific permissions.
  • /private/user/perms: ["read","write","move","delete","dot","get","admin"]
  • /public/perms: ["read","get"]
  • File-level tags include w: (copyparty's W-hash), up_by, up_ip, .up_at.

Data model

All content metadata lives in a single TSV at {cwd}/db/latest.tsv. The app reads this on startup into memory, performs all tag operations in-memory, then writes back the full TSV after each batch of changes.

DB file ID strategy

File IDs use copyparty's W-hash (w tag from ?ls JSON response). Every file listing entry includes a tags.w field — a base32/sha512-derived unique identifier for that file on that volume. This is more stable than filename or path, surviving renames and moves within the same mount.

Alternative considered: Generate UUIDv4 on first scan. Rejected — adds state not present in copyparty itself; W-hash already exists server-side and survives across sessions. For new files scanned from unmanaged, we fetch ?ls to get each file's W-tag before importing.

TSV format specification

Tab-separated values. Each row = one managed content item. Columns:

Col Name Type Description Example
0 file_id string copyparty W-hash of the file 72EJcK7mxEGM2ygG
1 original_name string Filename as uploaded to unmanaged (for display) IMG_1234.jpg
2 content_type enum image, video, animation image
3 similarity_hash hex-string phash output for fast nearest-neighbor search 0a1b2c3d... (64-char hex)
4 base_colors string Up to 4 hex colors, pipe-separated, each with percentage #f5a623_45,#234_15|... → TBD exact format
5 album_uuid uuid or empty v4 UUID string if assigned to album, empty otherwise 550e8400-e29b-41d4-a716-446655440000
6 album_position int or empty Zero-based position within the album group 3
7 user_tags string Comma-separated user-assigned tags, normalized to lowercase sunset,beach,golden hour
8 modified_at unix-timestamp Epoch seconds of last metadata modification (not file creation) 1784243166

File rows are appended on new-file scan. Full-file rewrite on bulk tag changes. The app maintains an in-memory index by file_id for O(1) lookup and avoids writing unchanged rows.

Color format specification

Base colors extracted via quantization of the downsampled image (k-means or median-cut, TBD). Stored as pipe-separated entries: {hex}_{percentage}. Example:

#e85d04_34,#f48c06_28,#faed00_18,#fffef2_12,#ef7d00_1,#fcffb8_1,#ff004d_1

This permits up to N colors (implementation will decide) while keeping the CSV row parseable. Percentage sums ≈ 100%.

UI Architecture

Single-page application. No build step — all JS/CSS served from /public/ on copyparty. Three views:

  1. Login (CWD selector) — directory input + Check/Enter buttons
  2. Tagging (main work view) — untagged queue + tag editor
  3. Gallery — browse filtered/sorted managed content

The app uses browser localStorage for persistence of CWD, password, and UI preferences. All content state lives server-side (copyparty filesystem). The TSV is the source of truth; the browser never "owns" data permanently.

### HEADER (top) ###
[last part of current working directory (centered)] [change working directory button (aligned right)]

### MAIN TAGGING UI ###
[open gallery button]

[grid of untagged images (images load into viewer when clicked)]
[previous item arrow] [image viewer] [next item arrow]
[album selector field (select from last albums, type to search, create new)]
[album position field (disabled unless an album is set)]
[tag input field (suggests existing, splits tags on comma)]

[similarity hash]
[base colors display (small)]
[delete file button]
### GALLERY ###
### HEADER (top) ###
[back button (to tagging ui)]
[tag filter picker (user tags/albums)]
[order-by picker (creation time/modification time/similarity/color similarity)]
[image grid (grouped by album)] [tag editor (sidebar) - allow for album/album position/user tag modifications when an image is clicked]
[context menu (right-click) - find similar by similarity hash/color, trash file]

Tag types

  1. DB modification time - timestamp of last tag modification time
  2. Content type - image/video/animation
  3. Similarity hash
  4. Base colors
  5. Album UUID - if set, content belongs to an ordered group of content items
  6. № in album - position of the item inside the group
  7. User tags - a comma-separated list of entries