Files
dcf/README.md
clawbot ed95e66f7d bring the repo up to org standards
Adopt the standard tooling: a Makefile of thin shims over a full
scripts-to-rule-them-all `script/` set, the canonical `.golangci.yml`
vendored byte-identical from `sneak/prompts`, docker-only linting via
`Dockerfile.lint`, a `Dockerfile` and Gitea workflow that gate every
push, and prettier/editorconfig/dockerignore config.

`make build` pointed at a `cmd/dcfinfo` that is not in the tree and
could never have succeeded; this repo is a library, so `build` is now
the compile check over every package.

Clear the 57 findings the canonical linter config reports on `pkg/dcf`.
Two were real: `findDCFMountPoints` checked a never-assigned `erro`
instead of the error from `findAllMountPoints`, discarding it, and
`privatePath` was computed twice so the first computation was dead.
Mountpoint selection otherwise behaves exactly as before; the defects
that survive are filed as issue #6, not fixed here.

Rename `DCFStore` to `Store` and `DCFObject` to `Object` (with its
`DCFStoreRoot` field to `StoreRoot`), which revive's stutter rule
requires and which the `fs.FS` rework in issue #4 will build on.

Replace the placeholder test with tests over the exported surface.
The filesystem walk stays uncovered: it is reachable only through
`GetDCFStores`, which needs real mounted media.

README keeps its content, reorganised into the required sections and
gaining Entrypoints.

(closes #1)
2026-08-30 11:36:58 +00:00

159 lines
6.1 KiB
Markdown

# dcf
`dcf` is a WTFPL-licensed Go library by [@sneak](https://sneak.berlin) that
reads the so-called "Design rule for Camera File system", or DCF, aka JEITA
(Japan Electronics and Information Technology Industries Association)
specification number CP-3461.
[wikipedia.org/wiki/Design_rule_for_Camera_File_system](https://en.wikipedia.org/wiki/Design_rule_for_Camera_File_system)
The DCF specification is why your digital camera puts images and videos in
`DCIM` and `PRIVATE/M4ROOT` directories on the memory card.
## Status
incomplete, under development, does not work yet
## Getting started
```sh
go get git.eeqj.de/sneak/dcf
```
```go
package main
import (
"fmt"
"git.eeqj.de/sneak/dcf/pkg/dcf"
)
func main() {
stores, err := dcf.GetDCFStores(0)
if err != nil {
panic(err)
}
for _, store := range *stores {
fmt.Println(store.String())
}
}
```
To work on the library itself:
```sh
git clone git@git.eeqj.de:sneak/dcf.git
cd dcf
script/setup # dependencies plus the git pre-commit hook
make check # test, lint, fmt-check
```
## Entrypoints
This repo adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
standard. Each `script/` entrypoint has a thin `make` shim; the scripts are
where the project's knowledge about flags, timeouts and docker-only linting
lives, so use them rather than invoking `go` or `golangci-lint` directly.
- `script/bootstrap` (`make bootstrap`) — install build dependencies (git, make,
go) idempotently. Linting additionally needs docker; markdown formatting needs
docker or a local `prettier`.
- `script/setup` (`make setup`) — `bootstrap` plus the git pre-commit hook.
- `script/test` (`make test`) — `go test` with the race detector and coverage,
`-count=1`, 90s timeout; quiet on success, verbose rerun on failure.
- `script/lint` (`make lint`) — `golangci-lint` via docker only, against the
digest-pinned image in `Dockerfile.lint`, then `script/assert-step-ran` and
`script/assert-context-complete` over the build log. Nothing is installed
locally and nothing runs on the host.
- `script/fmt` (`make fmt`) — format Go with `gofmt` and everything else with
`prettier` (writes).
- `script/fmt-check` (`make fmt-check`) — the same scope, read-only.
- `script/check` (`make check`) — `test` + `lint` + `fmt-check`. What the
pre-commit hook runs. Never modifies files.
- `script/docker` (`make docker`) — build the image, tagged with
`script/projectname`.
- `script/cibuild` (`make cibuild`) — the CI gate:
`docker build --progress=plain --no-cache-filter=lint --no-cache-filter=builder .`
followed by assertions that the lint step and the test step really ran, and
that both stages really received the whole repository. The Gitea workflow runs
this on every push.
- `script/precommit` — the hook body: `go mod tidy` must not change
`go.mod`/`go.sum`, then `check`.
- `script/install-precommit` (`make hooks`) — installs the hook.
- `script/projectname` — prints the project name; other scripts call it so they
can stay identical across repos.
- `script/prettier`, `script/assert-step-ran`, `script/assert-context-complete`
and `script/repo-source-manifest` are helpers, not entrypoints.
`make build`, `make deps` and `make clean` are the ordinary conveniences.
`make build` compiles every package; this repo is a library and ships no binary,
so there is no `cmd/` and nothing to link.
### Why the lint assertions exist
A green `docker build` is not evidence that the checks ran. On an unchanged tree
every layer comes from cache and the build exits 0 having executed nothing;
BuildKit silently ignores a `--no-cache-filter` naming a stage that no longer
exists; and a `.dockerignore` entry can remove a package from the build context,
after which the linter genuinely runs, genuinely examines what it was handed,
and genuinely reports `0 issues.` over a repository with a violation in it.
`script/assert-step-ran` and `script/assert-context-complete` close both holes;
read the comments in them before changing either.
## Rationale
I wanted to copy images off my memory cards for processing and like
overengineering and reusable code.
## Design
Everything lives in `pkg/dcf`, a single library package with no command
entrypoints.
- `dcf.go` — the public surface. `Store` is one DCF store (one mounted
filesystem); `Object` is one file in it, embedded in `Image` and `Video`.
`GetDCFStores` finds the stores on the system and walks each one, classifying
files by extension.
- `helpers.go` — mountpoint discovery. `findAllMountPoints` enumerates the
system's physical filesystems via `gopsutil`; `findDCFMountPoints` keeps only
those whose root holds a marker directory.
Detection is by directory name at the filesystem root, so no filesystem is ever
mounted and nothing outside a filesystem root is searched.
## Bugs
does not yet handle "dcf file groups" where multiple objects share the same
index number. if you need this, send me a link to a zip or tar of a memory card
that uses it and i'll see what i can do.
## TODO
The work in flight is on the tracker; this is the reading order for picking it
up.
- [#2](https://git.eeqj.de/sneak/dcf/issues/2) — parse the CP-3461 structure
rather than grepping every filesystem root for media extensions.
- [#3](https://git.eeqj.de/sneak/dcf/issues/3) — DCF file groups: objects
sharing an index number are one logical object.
- [#4](https://git.eeqj.de/sneak/dcf/issues/4) — API cleanup: `fs.FS`-based
store access, no pointer-to-slice returns, contexts on walks. This is also
what makes the filesystem walk testable; today it is reachable only through
`GetDCFStores`, which needs real mounted media, so it has no test coverage.
- [#5](https://git.eeqj.de/sneak/dcf/issues/5) — checksummed import off the
card, which is what the library is for.
- [#6](https://git.eeqj.de/sneak/dcf/issues/6) — mountpoint detection checks
`M4ROOT` at the filesystem root rather than `PRIVATE/M4ROOT`, and
`findDCFMountPoints` stops one store short of `requestedCount`.
## License
WTFPL. See [LICENSE](LICENSE).
## Author
sneak <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)>