feat(editor): new editor struct for acessing epub containers
This commit is contained in:
69
editor/container.go
Normal file
69
editor/container.go
Normal 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
114
editor/editor.go
Normal 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")
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
Reference in New Issue
Block a user