mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
* Add new txn manager interface * Add txn management to sqlite * Rename get to getByID * Add contexts to repository methods * Update query builders * Add context to reader writer interfaces * Use repository in resolver * Tighten interfaces * Tighten interfaces in dlna * Tighten interfaces in match package * Tighten interfaces in scraper package * Tighten interfaces in scan code * Tighten interfaces on autotag package * Remove ReaderWriter usage * Merge database package into sqlite
185 lines
4.7 KiB
Go
185 lines
4.7 KiB
Go
package manager
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/stashapp/stash/internal/manager/config"
|
|
"github.com/stashapp/stash/pkg/file"
|
|
"github.com/stashapp/stash/pkg/fsutil"
|
|
"github.com/stashapp/stash/pkg/gallery"
|
|
"github.com/stashapp/stash/pkg/hash/md5"
|
|
"github.com/stashapp/stash/pkg/image"
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stashapp/stash/pkg/plugin"
|
|
)
|
|
|
|
func (t *ScanTask) scanImage(ctx context.Context) {
|
|
var i *models.Image
|
|
path := t.file.Path()
|
|
|
|
if err := t.TxnManager.WithTxn(ctx, func(ctx context.Context) error {
|
|
var err error
|
|
i, err = t.TxnManager.Image.FindByPath(ctx, path)
|
|
return err
|
|
}); err != nil {
|
|
logger.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
scanner := image.Scanner{
|
|
Scanner: image.FileScanner(&file.FSHasher{}),
|
|
StripFileExtension: t.StripFileExtension,
|
|
TxnManager: t.TxnManager,
|
|
CreatorUpdater: t.TxnManager.Image,
|
|
CaseSensitiveFs: t.CaseSensitiveFs,
|
|
Paths: GetInstance().Paths,
|
|
PluginCache: instance.PluginCache,
|
|
MutexManager: t.mutexManager,
|
|
}
|
|
|
|
var err error
|
|
if i != nil {
|
|
i, err = scanner.ScanExisting(ctx, i, t.file)
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
i, err = scanner.ScanNew(ctx, t.file)
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
if i != nil {
|
|
if t.zipGallery != nil {
|
|
// associate with gallery
|
|
if err := t.TxnManager.WithTxn(ctx, func(ctx context.Context) error {
|
|
return gallery.AddImage(ctx, t.TxnManager.Gallery, t.zipGallery.ID, i.ID)
|
|
}); err != nil {
|
|
logger.Error(err.Error())
|
|
return
|
|
}
|
|
} else if config.GetInstance().GetCreateGalleriesFromFolders() {
|
|
// create gallery from folder or associate with existing gallery
|
|
logger.Infof("Associating image %s with folder gallery", i.Path)
|
|
var galleryID int
|
|
var isNewGallery bool
|
|
if err := t.TxnManager.WithTxn(ctx, func(ctx context.Context) error {
|
|
var err error
|
|
galleryID, isNewGallery, err = t.associateImageWithFolderGallery(ctx, i.ID, t.TxnManager.Gallery)
|
|
return err
|
|
}); err != nil {
|
|
logger.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
if isNewGallery {
|
|
GetInstance().PluginCache.ExecutePostHooks(ctx, galleryID, plugin.GalleryCreatePost, nil, nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if i != nil {
|
|
t.generateThumbnail(i)
|
|
}
|
|
}
|
|
|
|
type GalleryImageAssociator interface {
|
|
FindByPath(ctx context.Context, path string) (*models.Gallery, error)
|
|
Create(ctx context.Context, newGallery models.Gallery) (*models.Gallery, error)
|
|
gallery.ImageUpdater
|
|
}
|
|
|
|
func (t *ScanTask) associateImageWithFolderGallery(ctx context.Context, imageID int, qb GalleryImageAssociator) (galleryID int, isNew bool, err error) {
|
|
// find a gallery with the path specified
|
|
path := filepath.Dir(t.file.Path())
|
|
var g *models.Gallery
|
|
g, err = qb.FindByPath(ctx, path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if g == nil {
|
|
checksum := md5.FromString(path)
|
|
|
|
// create the gallery
|
|
currentTime := time.Now()
|
|
|
|
newGallery := models.Gallery{
|
|
Checksum: checksum,
|
|
Path: sql.NullString{
|
|
String: path,
|
|
Valid: true,
|
|
},
|
|
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
|
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
|
Title: sql.NullString{
|
|
String: fsutil.GetNameFromPath(path, false),
|
|
Valid: true,
|
|
},
|
|
}
|
|
|
|
logger.Infof("Creating gallery for folder %s", path)
|
|
g, err = qb.Create(ctx, newGallery)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
|
|
isNew = true
|
|
}
|
|
|
|
// associate image with gallery
|
|
err = gallery.AddImage(ctx, qb, g.ID, imageID)
|
|
galleryID = g.ID
|
|
return
|
|
}
|
|
|
|
func (t *ScanTask) generateThumbnail(i *models.Image) {
|
|
if !t.GenerateThumbnails {
|
|
return
|
|
}
|
|
|
|
thumbPath := GetInstance().Paths.Generated.GetThumbnailPath(i.Checksum, models.DefaultGthumbWidth)
|
|
exists, _ := fsutil.FileExists(thumbPath)
|
|
if exists {
|
|
return
|
|
}
|
|
|
|
config, _, err := image.DecodeSourceImage(i)
|
|
if err != nil {
|
|
logger.Errorf("error reading image %s: %s", i.Path, err.Error())
|
|
return
|
|
}
|
|
|
|
if config.Height > models.DefaultGthumbWidth || config.Width > models.DefaultGthumbWidth {
|
|
encoder := image.NewThumbnailEncoder(instance.FFMPEG)
|
|
data, err := encoder.GetThumbnail(i, models.DefaultGthumbWidth)
|
|
|
|
if err != nil {
|
|
// don't log for animated images
|
|
if !errors.Is(err, image.ErrNotSupportedForThumbnail) {
|
|
logger.Errorf("error getting thumbnail for image %s: %s", i.Path, err.Error())
|
|
|
|
var exitErr *exec.ExitError
|
|
if errors.As(err, &exitErr) {
|
|
logger.Errorf("stderr: %s", string(exitErr.Stderr))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
err = fsutil.WriteFile(thumbPath, data)
|
|
if err != nil {
|
|
logger.Errorf("error writing thumbnail for image %s: %s", i.Path, err)
|
|
}
|
|
}
|
|
}
|