mirror of
https://github.com/Belphemur/CBZOptimizer.git
synced 2026-07-21 19:05:39 +02:00
feat(watch): add opt-in --backfill flag for existing archives
Co-authored-by: Belphemur <197810+Belphemur@users.noreply.github.com>
This commit is contained in:
co-authored by
Belphemur
parent
0b3c3fa90c
commit
1d981be21b
@@ -78,13 +78,15 @@ Watch a folder for new CBZ/CBR files and optimize them automatically:
|
|||||||
cbzconverter watch [folder] --quality 85 --override --format webp --split
|
cbzconverter watch [folder] --quality 85 --override --format webp --split
|
||||||
```
|
```
|
||||||
|
|
||||||
Watch mode only reacts to filesystem events that occur *after* it starts; it does not
|
Watch mode only reacts to filesystem events that occur *after* it starts; by default it does
|
||||||
scan and optimize files that already exist in the folder when it starts. Run the
|
not scan and optimize files that already exist in the folder when it starts. Run the
|
||||||
`optimize` command first if you need to process an existing library, then use `watch`
|
`optimize` command first if you need to process an existing library, then use `watch`
|
||||||
to keep it up to date going forward. The only exception is a directory that gets
|
to keep it up to date going forward, or pass `--backfill` to have `watch` optimize the
|
||||||
created/moved into the watched tree while watch mode is running: since no per-file
|
existing files at startup before it begins watching for new changes. The only exception
|
||||||
event is emitted for files already inside it, its existing archives are processed once
|
is a directory that gets created/moved into the watched tree while watch mode is
|
||||||
when the directory is first detected.
|
running: since no per-file event is emitted for files already inside it, its existing
|
||||||
|
archives are always processed once when the directory is first detected, regardless of
|
||||||
|
`--backfill`.
|
||||||
|
|
||||||
Or with Docker:
|
Or with Docker:
|
||||||
|
|
||||||
@@ -102,6 +104,7 @@ docker run -v /path/to/comics:/comics ghcr.io/belphemur/cbzoptimizer:latest watc
|
|||||||
- Can be specified as: `--format webp`, `-f webp`, or `--format=webp`
|
- Can be specified as: `--format webp`, `-f webp`, or `--format=webp`
|
||||||
- Case-insensitive: `webp`, `WEBP`, and `WebP` are all valid
|
- Case-insensitive: `webp`, `WEBP`, and `WebP` are all valid
|
||||||
- `--timeout`, `-t`: Maximum time allowed for converting a single chapter (e.g., 30s, 5m, 1h). 0 means no timeout. Default is 0.
|
- `--timeout`, `-t`: Maximum time allowed for converting a single chapter (e.g., 30s, 5m, 1h). 0 means no timeout. Default is 0.
|
||||||
|
- `--backfill`: *(`watch` only)* Optimize CBZ/CBR files that already exist in the watched folder at startup, before watching for new changes. Default is false.
|
||||||
- `--log`, `-l`: Set log level; can be 'panic', 'fatal', 'error', 'warn', 'info', 'debug', or 'trace'. Default is info.
|
- `--log`, `-l`: Set log level; can be 'panic', 'fatal', 'error', 'warn', 'info', 'debug', or 'trace'. Default is info.
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ func init() {
|
|||||||
// Setup common flags (format, quality, override, split, timeout) with viper binding
|
// Setup common flags (format, quality, override, split, timeout) with viper binding
|
||||||
setupCommonFlags(command, &converterType, 85, true, false, true)
|
setupCommonFlags(command, &converterType, 85, true, false, true)
|
||||||
|
|
||||||
|
command.Flags().Bool("backfill", false, "Optimize CBZ/CBR files that already exist in the watched folder at startup, before watching for new changes")
|
||||||
|
_ = viper.BindPFlag("backfill", command.Flags().Lookup("backfill"))
|
||||||
|
|
||||||
AddCommand(command)
|
AddCommand(command)
|
||||||
}
|
}
|
||||||
func WatchCommand(_ *cobra.Command, args []string) error {
|
func WatchCommand(_ *cobra.Command, args []string) error {
|
||||||
@@ -64,6 +67,8 @@ func WatchCommand(_ *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
timeout := viper.GetDuration("timeout")
|
timeout := viper.GetDuration("timeout")
|
||||||
|
|
||||||
|
backfill := viper.GetBool("backfill")
|
||||||
|
|
||||||
converterType := constant.FindConversionFormat(viper.GetString("format"))
|
converterType := constant.FindConversionFormat(viper.GetString("format"))
|
||||||
chapterConverter, err := converter.Get(converterType)
|
chapterConverter, err := converter.Get(converterType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -74,7 +79,7 @@ func WatchCommand(_ *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to prepare converter: %w", err)
|
return fmt.Errorf("failed to prepare converter: %w", err)
|
||||||
}
|
}
|
||||||
log.Info().Str("path", path).Bool("override", override).Uint8("quality", quality).Str("format", converterType.String()).Bool("split", split).Msg("Watching directory")
|
log.Info().Str("path", path).Bool("override", override).Uint8("quality", quality).Str("format", converterType.String()).Bool("split", split).Bool("backfill", backfill).Msg("Watching directory")
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,15 +106,18 @@ func WatchCommand(_ *cobra.Command, args []string) error {
|
|||||||
defer debouncer.Stop()
|
defer debouncer.Stop()
|
||||||
|
|
||||||
// Note: existing archives already present under path when the watch
|
// Note: existing archives already present under path when the watch
|
||||||
// starts are intentionally left untouched. Watch mode only reacts to
|
// starts are left untouched unless --backfill is set. Watch mode only
|
||||||
// filesystem events going forward; use the `optimize` command to process
|
// reacts to filesystem events going forward by default; use the
|
||||||
// a library's existing contents. Archives inside a directory that is
|
// `optimize` command (or pass --backfill) to process a library's
|
||||||
// created/moved into the watched tree *after* startup are still
|
// existing contents. Archives inside a directory that is created/moved
|
||||||
// back-filled below, since only the directory itself generates an
|
// into the watched tree *after* startup are always back-filled below,
|
||||||
// fsnotify event.
|
// since only the directory itself generates an fsnotify event.
|
||||||
if err := addRecursiveWatch(watcher, path); err != nil {
|
if err := addRecursiveWatch(watcher, path); err != nil {
|
||||||
return fmt.Errorf("failed to watch path %s: %w", path, err)
|
return fmt.Errorf("failed to watch path %s: %w", path, err)
|
||||||
}
|
}
|
||||||
|
if backfill {
|
||||||
|
backfillExistingArchives(path, debouncer.Trigger)
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -170,3 +170,37 @@ func TestOptimizeQueueSkipsMissingPath(t *testing.T) {
|
|||||||
|
|
||||||
assert.EqualValues(t, 1, atomic.LoadInt32(&calls))
|
assert.EqualValues(t, 1, atomic.LoadInt32(&calls))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWatchCommandBackfillFlagDefaultsToFalse(t *testing.T) {
|
||||||
|
watchCmd, _, err := rootCmd.Find([]string{"watch"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
flag := watchCmd.Flags().Lookup("backfill")
|
||||||
|
require.NotNil(t, flag, "watch command should register a --backfill flag")
|
||||||
|
assert.Equal(t, "false", flag.DefValue)
|
||||||
|
assert.Equal(t, "bool", flag.Value.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackfillExistingArchivesOnlyInvokedWhenRequested(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(root, "chapter1.cbz"), []byte("data"), 0o644))
|
||||||
|
|
||||||
|
runBackfill := func(backfill bool) []string {
|
||||||
|
var found []string
|
||||||
|
var mu sync.Mutex
|
||||||
|
process := func(path string) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
found = append(found, path)
|
||||||
|
}
|
||||||
|
// Mirrors the gating logic in WatchCommand: backfillExistingArchives
|
||||||
|
// must only run when the --backfill flag is set.
|
||||||
|
if backfill {
|
||||||
|
backfillExistingArchives(root, process)
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Empty(t, runBackfill(false), "no pre-existing archive should be processed when backfill is disabled")
|
||||||
|
assert.Len(t, runBackfill(true), 1, "pre-existing archives should be processed when backfill is enabled")
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ CBZOptimizer is a Go CLI that optimizes comic archives (`.cbz` and `.cbr`) by co
|
|||||||
|
|
||||||
## Watch mode
|
## Watch mode
|
||||||
|
|
||||||
`watch` monitors a directory tree for archive file changes and runs optimization automatically.
|
`watch` monitors a directory tree for archive file changes and runs optimization automatically. By default it only reacts to changes going forward; pass `--backfill` to also optimize archives that already exist in the folder at startup.
|
||||||
|
|
||||||
## Key runtime requirements
|
## Key runtime requirements
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user