Skip to content

Latest commit

 

History

History
83 lines (59 loc) · 4.67 KB

File metadata and controls

83 lines (59 loc) · 4.67 KB

AGENTS.md

Project Overview

Jomics is a self-contained comic reader web server written in Go. It serves CBZ and CBR comic archives via a browser UI with no external dependencies beyond libc and the kernel. Everything (templates, static assets, CSS) is embedded into the binary at build time via go:embed.

This is a single-file project — all Go code lives in main.go (~665 lines).

Build & Run

go build                                    # produces ./jomics binary
./jomics -root /path/to/comics/collection   # required flag, serves on localhost:4531

Important: go-unarr (used for CBZ/CBR decompression) requires CGO and libc. You cannot build a fully static binary.

Key Flags

Flag Default Description
-root (required) Root directory of comic collection
-addr localhost:4531 Server address. Use :4531 to bind all interfaces
-webroot (empty) Path prefix for reverse proxy setups
-si 300 Rescan interval in seconds (0/negative disables)
-th 400 Front cover thumbnail height (100-2000)
-light false Use light theme (default is dark)
-q false Quiet mode

Architecture

Data Flow

  1. On startup, scanCollection() walks the root directory recursively, discovering .cbz, .cbr files and subdirectories
  2. Each file/directory is assigned a CRC32 hash (IEEE table) of its full path — this hash is the album identifier used in all URLs
  3. prepareAlbums() extracts the first image from each archive, resizes it to the thumbnail height, and caches it to the XDG cache directory
  4. A background goroutine periodically re-scans the collection (default every 5 minutes)
  5. All HTTP handler methods hold colMutex during archive reads — collection swap on rescan is atomic

URL Structure (query-parameter based, not path-based)

  • /albums/?folder=0x{hash} — list comics in a folder
  • /covers/?album=0x{hash} — serve front cover thumbnail
  • /read/?album=0x{hash}&folder=0x{hash}&page=N — render page view
  • /images/?album=0x{hash}&folder=0x{hash}&page=N — serve raw page image

Album and folder identifiers are hex-encoded CRC32 hashes passed as 0x prefixed hex strings in query parameters.

Embedded Resources

Three go:embed directives bundle assets:

  • tmpl/ — HTML templates (frontcover.html, page.html)
  • static/ — PNG assets (favicon, folder icon, logos)
  • css/pico-master.zip — Pico CSS framework, served at runtime via zipfs (from gerace.dev/zipfs)

Cache

Thumbnail cache lives in the XDG cache directory (~/.cache/jomics/ on Linux, per the gmelchett vendor name). Cache filenames are {crc32hex}-{thumbHeight}. Changing -th invalidates existing thumbnails.

ComicInfo.xml

If a CBZ/CBR archive contains ComicInfo.xml, its <Series>, <Title>, <Number>, and <Year> fields are used to construct the display title (e.g., "Series Title Number (Year)"). Otherwise, the filename (underscores replaced with spaces, title-cased) is used.

Code Conventions

  • Single package main — no internal packages
  • No tests exist
  • No linter or formatter configuration
  • Go 1.17 module (check go.mod before using newer Go features)
  • Struct types for HTTP handler data (Page, Albums, FrontCover)
  • Method receivers on jomics struct for HTTP handlers; on comicCollection for collection operations
  • All HTTP routing via http.HandleFunc / http.Handle on the default mux
  • Navigation between pages uses HTML form POSTs (not links) with hardcoded form actions

Gotchas

  • strings.Title is deprecated (used at main.go:246). A gopls hint flags this. Replacement uses golang.org/x/text/cases.
  • CGO dependency: go-unarr wraps libunarr (C library). Cross-compilation requires the C toolchain for the target platform.
  • No Content-Type for cover images: handleFrontImage sets application/octet-stream rather than image/jpeg/image/png. This works but is not ideal.
  • WriteHeader before Header().Set() in handleFrontImage (main.go:485-486) — headers set after WriteHeader are ignored by the HTTP spec. This is a latent bug.
  • Thread safety: The colMutex protects the collection pointer during reads, but the periodic rescan creates a brand new comicCollection and swaps it atomically under the lock.
  • Folder as root: main() passes filepath.Dir(*root) as rootDir, but scanCollection walks *root directly. The directory containing the comics directory itself is the root of the walk.
  • rootDir uses filepath.Dir: This means the initial directory listing shows contents one level up from -root. This may be intentional for the navigation model.