115 lines
2.2 KiB
Go
115 lines
2.2 KiB
Go
|
|
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")
|
||
|
|
)
|