diff --git a/kepub/convert.go b/kepub/convert.go index 4fabe0e..8a8ff84 100644 --- a/kepub/convert.go +++ b/kepub/convert.go @@ -32,10 +32,16 @@ func (c *Converter) Convert(ctx context.Context, w io.Writer, r fs.FS) error { FileActionTransformOPF = 3 ) + p := ctxProgress(ctx) + if tmp, ok := r.(*zip.ReadCloser); ok { r = &tmp.Reader } + if p != nil { + p(true, 0, 0) + } + var files []*zip.FileHeader if zr, ok := r.(*zip.Reader); ok { // if it's a zip, preserve the original FileHeaders @@ -268,6 +274,7 @@ func (c *Converter) Convert(ctx context.Context, w io.Writer, r fs.FS) error { } // write the files + var n int for of := range output { if of.Index == -1 { if err := zipReplace(zw, of.Header, of.Bytes); err != nil { @@ -296,6 +303,10 @@ func (c *Converter) Convert(ctx context.Context, w io.Writer, r fs.FS) error { b.Reset() pool.Put(b) } + if p != nil { + n++ + p(false, n, len(files)) + } } if err := g.Wait(); err != nil { return err @@ -306,6 +317,9 @@ func (c *Converter) Convert(ctx context.Context, w io.Writer, r fs.FS) error { return fmt.Errorf("finalize output EPUB: %w", err) } + if p != nil { + p(true, n, n) + } return nil } diff --git a/kepub/kepub.go b/kepub/kepub.go index 05987c6..333b78c 100644 --- a/kepub/kepub.go +++ b/kepub/kepub.go @@ -1,6 +1,11 @@ // Package kepub converts EPUBs to KEPUBs. package kepub +import ( + "context" + "math" +) + // Converter converts EPUB2/EPUB3 books to Kobo's KEPUB format. type Converter struct { // extra css @@ -120,3 +125,50 @@ body>div { padding-left: 0.2em !important; padding-right: 0.2em !important; }` + +// These are for use by certain kepubify frontends for progress information +// during conversions. It is not exported for general use, must be imported via +// an unsafe go:linkname directive, and is subject to change. +type ( + progressKey struct{} + progressDeltaKey struct{} +) + +// withProgress adds a function to be called synchronously as the conversion +// progresses. If delta is in the range [0, 1], the callback will be +// rate-limited to when there is an important change or when the percentage +// changes by more than delta. +func withProgress(ctx context.Context, delta float64, fn func(n, total int)) context.Context { + ctx = context.WithValue(ctx, progressDeltaKey{}, delta) + ctx = context.WithValue(ctx, progressKey{}, fn) + return ctx +} + +// ctxProgress creates a rate-limited progress callback for the provided +// context. It returns nil if a callback has not been set. +func ctxProgress(ctx context.Context) func(force bool, n, total int) { + if v := ctx.Value(progressKey{}); v != nil { + fn := v.(func(n, total int)) + dt := ctx.Value(progressDeltaKey{}).(float64) + + var pct, lastPct float64 + return func(force bool, n, total int) { + if dt < 0 || dt > 1 { + fn(n, total) + return + } + if n == 0 && total == 0 { + pct = 1 + } else if n == 0 || total == 0 { + pct = 0 + } else { + pct = float64(n) / float64(total) + } + if force || math.Abs(pct-lastPct) >= dt { + lastPct = pct + fn(n, total) + } + } + } + return nil +}