Have got session creation working
This commit is contained in:
parent
18f9f49c0a
commit
036b683eab
10 changed files with 438 additions and 3 deletions
91
services/imgedit/processing.go
Normal file
91
services/imgedit/processing.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package imgedit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
)
|
||||
|
||||
func (s *Service) reprocess(ctx context.Context, session models.ImageEditSession) (imageSource, error) {
|
||||
var img imageSource
|
||||
|
||||
for _, p := range session.Processors {
|
||||
// Check if there's currently a cached image of this processor
|
||||
cachedImageFile := filepath.Join(s.scratchDir, session.GUID, fmt.Sprintf("%v.%v", p.VersionID, session.ImageExt))
|
||||
if s, err := os.Stat(cachedImageFile); err == nil && !s.IsDir() {
|
||||
img = fileImageSource(cachedImageFile)
|
||||
continue
|
||||
}
|
||||
|
||||
// Need to process the image
|
||||
var srcImg image.Image
|
||||
if img != nil {
|
||||
var err error
|
||||
srcImg, err = img.image()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resImg, err := s.processImage(ctx, srcImg, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache the processed image
|
||||
if err := imaging.Save(resImg, cachedImageFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img = imageImageSource{resImg}
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (s *Service) processImage(ctx context.Context, srcImg image.Image, processor models.ImageEditProcessor) (image.Image, error) {
|
||||
switch processor.Type {
|
||||
case "copy-upload":
|
||||
var p models.CopyUploadProps
|
||||
if err := json.Unmarshal(processor.Props, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, rc, err := s.uploadService.OpenUpload(ctx, p.UploadID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := rc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return imaging.Decode(f)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown processor type: %v", processor.Type)
|
||||
}
|
||||
|
||||
type imageSource interface {
|
||||
image() (image.Image, error)
|
||||
}
|
||||
|
||||
type fileImageSource string
|
||||
|
||||
func (f fileImageSource) image() (image.Image, error) {
|
||||
return imaging.Open(string(f))
|
||||
}
|
||||
|
||||
type imageImageSource struct {
|
||||
img image.Image
|
||||
}
|
||||
|
||||
func (i imageImageSource) image() (image.Image, error) {
|
||||
return i.img, nil
|
||||
}
|
||||
116
services/imgedit/service.go
Normal file
116
services/imgedit/service.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package imgedit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
"lmika.dev/lmika/weiro/services/uploads"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
scratchDir string
|
||||
uploadService *uploads.Service
|
||||
sessionStore *sessionStore
|
||||
}
|
||||
|
||||
func New(
|
||||
uploadService *uploads.Service,
|
||||
scratchDir string,
|
||||
) *Service {
|
||||
return &Service{
|
||||
scratchDir: scratchDir,
|
||||
uploadService: uploadService,
|
||||
sessionStore: &sessionStore{baseDir: scratchDir},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) LoadImageVersion(ctx context.Context, sessionID string, versionID string) (mimeType string, rw func() (io.ReadCloser, error), err error) {
|
||||
site, user, err := s.fetchSiteAndUser(ctx)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
session, err := s.sessionStore.get(sessionID)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
} else if session.SiteID != site.ID || session.UserID != user.ID {
|
||||
return "", nil, models.PermissionError
|
||||
}
|
||||
|
||||
return s.sessionStore.getImage(session, versionID+"."+session.ImageExt)
|
||||
}
|
||||
|
||||
func (s *Service) NewSession(ctx context.Context, baseUploadID int64) (models.ImageEditSession, error) {
|
||||
site, user, err := s.fetchSiteAndUser(ctx)
|
||||
if err != nil {
|
||||
return models.ImageEditSession{}, err
|
||||
}
|
||||
|
||||
upload, _, err := s.uploadService.OpenUpload(ctx, baseUploadID)
|
||||
if err != nil {
|
||||
return models.ImageEditSession{}, err
|
||||
}
|
||||
|
||||
var ext string
|
||||
switch upload.MIMEType {
|
||||
case "image/jpeg":
|
||||
ext = "jpg"
|
||||
case "image/png":
|
||||
ext = "png"
|
||||
default:
|
||||
return models.ImageEditSession{}, models.UnsupportedImageFormat
|
||||
}
|
||||
|
||||
newSession := models.ImageEditSession{
|
||||
GUID: models.NewNanoID(),
|
||||
SiteID: site.ID,
|
||||
UserID: user.ID,
|
||||
BaseUploadID: baseUploadID,
|
||||
ImageExt: ext,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Processors: []models.ImageEditProcessor{
|
||||
{
|
||||
Type: "copy-upload",
|
||||
Props: mustToJSON(models.CopyUploadProps{UploadID: baseUploadID}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
newSession.RecalcVersionIDs()
|
||||
if err := s.sessionStore.create(newSession); err != nil {
|
||||
return models.ImageEditSession{}, err
|
||||
}
|
||||
|
||||
if _, err := s.reprocess(ctx, newSession); err != nil {
|
||||
return models.ImageEditSession{}, err
|
||||
}
|
||||
|
||||
return newSession, nil
|
||||
}
|
||||
|
||||
func (s *Service) fetchSiteAndUser(ctx context.Context) (models.Site, models.User, error) {
|
||||
user, ok := models.GetUser(ctx)
|
||||
if !ok {
|
||||
return models.Site{}, models.User{}, models.UserRequiredError
|
||||
}
|
||||
|
||||
site, ok := models.GetSite(ctx)
|
||||
if !ok {
|
||||
return models.Site{}, models.User{}, models.SiteRequiredError
|
||||
}
|
||||
|
||||
if site.OwnerID != user.ID {
|
||||
return models.Site{}, models.User{}, models.PermissionError
|
||||
}
|
||||
|
||||
return site, user, nil
|
||||
}
|
||||
|
||||
func mustToJSON(a any) json.RawMessage {
|
||||
b, _ := json.Marshal(a)
|
||||
return b
|
||||
}
|
||||
66
services/imgedit/store.go
Normal file
66
services/imgedit/store.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package imgedit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
)
|
||||
|
||||
type sessionStore struct {
|
||||
baseDir string
|
||||
}
|
||||
|
||||
func (ss *sessionStore) create(newSession models.ImageEditSession) error {
|
||||
sessionMeta, err := json.Marshal(newSession)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(ss.baseDir, newSession.GUID), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(ss.baseDir, newSession.GUID, "session.json"), sessionMeta, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sessionStore) get(guid string) (models.ImageEditSession, error) {
|
||||
sessionDataBts, err := os.ReadFile(filepath.Join(ss.baseDir, guid, "session.json"))
|
||||
if err != nil {
|
||||
return models.ImageEditSession{}, err
|
||||
}
|
||||
|
||||
sessionData := models.ImageEditSession{}
|
||||
if err := json.Unmarshal(sessionDataBts, &sessionData); err != nil {
|
||||
return models.ImageEditSession{}, err
|
||||
}
|
||||
|
||||
return sessionData, nil
|
||||
}
|
||||
|
||||
func (ss *sessionStore) getImage(session models.ImageEditSession, imageFilename string) (string, func() (io.ReadCloser, error), error) {
|
||||
fullPath := filepath.Join(ss.baseDir, session.GUID, imageFilename)
|
||||
if s, err := os.Stat(fullPath); err != nil {
|
||||
return "", nil, err
|
||||
} else if s.IsDir() {
|
||||
return "", nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
var mimeType string
|
||||
switch filepath.Ext(imageFilename) {
|
||||
case ".jpg", ".jpeg":
|
||||
mimeType = "image/jpeg"
|
||||
case ".png":
|
||||
mimeType = "image/png"
|
||||
default:
|
||||
return "", nil, models.UnsupportedImageFormat
|
||||
}
|
||||
|
||||
return mimeType, func() (io.ReadCloser, error) {
|
||||
return os.Open(fullPath)
|
||||
}, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue