feat(editor): new editor struct for acessing epub containers

This commit is contained in:
Guz
2025-11-20 15:12:56 -03:00
parent aecb142a26
commit 8d75630f5c
4 changed files with 192 additions and 0 deletions

69
editor/container.go Normal file
View File

@@ -0,0 +1,69 @@
package editor
import (
"bytes"
_ "embed"
"encoding/xml"
"errors"
"fmt"
"log/slog"
"path"
"sync"
"time"
"code.capytal.cc/capytal/comicverse/editor/epub"
"code.capytal.cc/capytal/comicverse/editor/internals/shortid"
"code.capytal.cc/capytal/comicverse/editor/storage"
"code.capytal.cc/loreddev/x/tinyssert"
"github.com/google/uuid"
"golang.org/x/net/html"
)
type Container struct {
id uuid.UUID
pkg epub.Package
storage storage.Storage
log *slog.Logger
assert tinyssert.Assertions
flushed bool
}
func (p *Container) Flush() error {
p.assert.NotZero(p.pkg, "invalid ePUB: package must be set")
p.assert.NotZero(p.pkg.Metadata, "invalid ePUB: package must have metadata")
p.assert.NotZero(p.pkg.Metadata.ID, "invalid ePUB: ID must always be specified")
p.assert.NotZero(p.pkg.Metadata.Language, "invalid ePUB: Language must always be specified")
p.assert.NotZero(p.pkg.Metadata.Title, "invalid ePUB: Title must always be specified")
p.mu.Lock()
defer p.mu.Unlock()
p.log.Debug("Flushing state of publication")
if p.flushed {
p.log.Debug("Publication doesn't have unsaved changes, skipping flush")
return nil
}
defer p.log.Debug("Publication's state flushed")
b, err := xml.MarshalIndent(p.pkg, "", " ")
if err != nil {
return fmt.Errorf("editor.Publication: failed to marshal package: %w", err)
}
if _, err = p.storage.Write("content.opf", b); err != nil {
return fmt.Errorf("editor.Publication: failed to write content.opf: %w", err)
}
return nil
}

114
editor/editor.go Normal file
View File

@@ -0,0 +1,114 @@
package editor
import (
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"log/slog"
"sync"
"time"
"code.capytal.cc/capytal/comicverse/editor/epub"
"code.capytal.cc/capytal/comicverse/editor/storage"
"code.capytal.cc/loreddev/x/tinyssert"
"github.com/google/uuid"
"golang.org/x/text/language"
)
func New(
storage storage.Storage,
logger *slog.Logger,
assert tinyssert.Assertions,
) *Editor {
assert.NotZero(storage)
assert.NotZero(logger)
return &Editor{
storage: storage,
log: logger,
assert: assert,
}
}
type Editor struct {
storage storage.Storage
ctx context.Context
log *slog.Logger
assert tinyssert.Assertions
}
func (e *Editor) New(id uuid.UUID, title string, lang language.Tag) (*Container, error) {
f := fmt.Sprintf("%s/content.opf", id)
if e.storage.Exists(f) {
return nil, ErrAlreadyExists
}
pub := &Container{
id: id,
pkg: epub.Package{
Metadata: epub.Metadata{
ID: fmt.Sprintf("comicverse:%s", id),
Title: title,
Language: lang,
Date: time.Now(),
Modified: time.Now(),
},
},
log: e.log.WithGroup(fmt.Sprintf("publication:%s", id)),
assert: e.assert,
storage: storage.WithRoot(id.String(), e.storage),
}
err := pub.Flush()
if err != nil {
return nil, fmt.Errorf("editor: unable to flush changes of publication: %w", err)
}
return pub, nil
}
func (e *Editor) Open(id uuid.UUID) (*Container, error) {
content, err := e.storage.Open(fmt.Sprintf("%s/content.opf", id))
if errors.Is(err, storage.ErrNotExists) {
return nil, ErrNotExists
} else if err != nil {
return nil, fmt.Errorf("editor: unable to open package: %w", err)
}
b, err := io.ReadAll(content)
if err != nil {
return nil, fmt.Errorf("editor: unable to read contents of package: %w", err)
}
var pkg epub.Package
err = xml.Unmarshal(b, &pkg)
if err != nil {
return nil, fmt.Errorf("editor: unable to decode xml of package: %w", err)
}
c := &Container{
id: id,
pkg: pkg,
log: e.log.WithGroup(fmt.Sprintf("publication:%s", id)),
assert: e.assert,
storage: storage.WithRoot(id.String(), e.storage),
}
return c, nil
}
var (
ErrAlreadyExists = errors.New("editor: file already exists")
ErrNotExists = errors.New("editor: file doesn't exist")
)

View File

@@ -5,4 +5,7 @@ go 1.25.2
require (
code.capytal.cc/loreddev/smalltrip v0.0.0-20251113171745-e3813daa807e
code.capytal.cc/loreddev/x v0.0.0-20251113171626-2ce5d71249c1
github.com/google/uuid v1.6.0
golang.org/x/net v0.47.0
golang.org/x/text v0.31.0
)

View File

@@ -2,3 +2,9 @@ code.capytal.cc/loreddev/smalltrip v0.0.0-20251113171745-e3813daa807e h1:LdkirHD
code.capytal.cc/loreddev/smalltrip v0.0.0-20251113171745-e3813daa807e/go.mod h1:jMvSPUj295pTk/ixyxZfwZJE/RQ7DZzvQ3cVoAklkPA=
code.capytal.cc/loreddev/x v0.0.0-20251113171626-2ce5d71249c1 h1:BE0QdvwVVTG/t7nwNO5rrLf1vdAc5axv/1mWd/oAWhw=
code.capytal.cc/loreddev/x v0.0.0-20251113171626-2ce5d71249c1/go.mod h1:p5ZPHzutdbUDfpvNBCjv5ls6rM4YNl2k4ipD5b0aRho=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=