mirror of https://github.com/stashapp/stash.git
1054 lines
28 KiB
Go
1054 lines
28 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/stashapp/stash/internal/manager"
|
|
"github.com/stashapp/stash/pkg/file"
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stashapp/stash/pkg/plugin"
|
|
"github.com/stashapp/stash/pkg/plugin/hook"
|
|
"github.com/stashapp/stash/pkg/scene"
|
|
"github.com/stashapp/stash/pkg/sliceutil"
|
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
|
"github.com/stashapp/stash/pkg/utils"
|
|
)
|
|
|
|
// used to refetch scene after hooks run
|
|
func (r *mutationResolver) getScene(ctx context.Context, id int) (ret *models.Scene, err error) {
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
ret, err = r.repository.Scene.Find(ctx, id)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCreateInput) (ret *models.Scene, err error) {
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
fileIDs, err := translator.fileIDSliceFromStringSlice(input.FileIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting file ids: %w", err)
|
|
}
|
|
|
|
// Populate a new scene from the input
|
|
newScene := models.NewScene()
|
|
|
|
newScene.Title = translator.string(input.Title)
|
|
newScene.Code = translator.string(input.Code)
|
|
newScene.Details = translator.string(input.Details)
|
|
newScene.Director = translator.string(input.Director)
|
|
newScene.Rating = input.Rating100
|
|
newScene.Organized = translator.bool(input.Organized)
|
|
newScene.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
|
|
|
newScene.Date, err = translator.datePtr(input.Date)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting date: %w", err)
|
|
}
|
|
newScene.StudioID, err = translator.intPtrFromString(input.StudioID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
|
}
|
|
|
|
if input.Urls != nil {
|
|
newScene.URLs = models.NewRelatedStrings(input.Urls)
|
|
} else if input.URL != nil {
|
|
newScene.URLs = models.NewRelatedStrings([]string{*input.URL})
|
|
}
|
|
|
|
newScene.PerformerIDs, err = translator.relatedIds(input.PerformerIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting performer ids: %w", err)
|
|
}
|
|
newScene.TagIDs, err = translator.relatedIds(input.TagIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
|
}
|
|
newScene.GalleryIDs, err = translator.relatedIds(input.GalleryIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
|
}
|
|
|
|
newScene.Movies, err = translator.relatedMovies(input.Movies)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting movies: %w", err)
|
|
}
|
|
|
|
var coverImageData []byte
|
|
if input.CoverImage != nil {
|
|
var err error
|
|
coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing cover image: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
ret, err = r.Resolver.sceneService.Create(ctx, &newScene, fileIDs, coverImageData)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUpdateInput) (ret *models.Scene, err error) {
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
// Start the transaction and save the scene
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
ret, err = r.sceneUpdate(ctx, input, translator)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r.hookExecutor.ExecutePostHooks(ctx, ret.ID, hook.SceneUpdatePost, input, translator.getFields())
|
|
return r.getScene(ctx, ret.ID)
|
|
}
|
|
|
|
func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.SceneUpdateInput) (ret []*models.Scene, err error) {
|
|
inputMaps := getUpdateInputMaps(ctx)
|
|
|
|
// Start the transaction and save the scenes
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
for i, scene := range input {
|
|
translator := changesetTranslator{
|
|
inputMap: inputMaps[i],
|
|
}
|
|
|
|
thisScene, err := r.sceneUpdate(ctx, *scene, translator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ret = append(ret, thisScene)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// execute post hooks outside of txn
|
|
var newRet []*models.Scene
|
|
for i, scene := range ret {
|
|
translator := changesetTranslator{
|
|
inputMap: inputMaps[i],
|
|
}
|
|
|
|
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, hook.SceneUpdatePost, input, translator.getFields())
|
|
|
|
scene, err = r.getScene(ctx, scene.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newRet = append(newRet, scene)
|
|
}
|
|
|
|
return newRet, nil
|
|
}
|
|
|
|
func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTranslator) (*models.ScenePartial, error) {
|
|
updatedScene := models.NewScenePartial()
|
|
|
|
updatedScene.Title = translator.optionalString(input.Title, "title")
|
|
updatedScene.Code = translator.optionalString(input.Code, "code")
|
|
updatedScene.Details = translator.optionalString(input.Details, "details")
|
|
updatedScene.Director = translator.optionalString(input.Director, "director")
|
|
updatedScene.Rating = translator.optionalInt(input.Rating100, "rating100")
|
|
|
|
if input.OCounter != nil {
|
|
logger.Warnf("o_counter is deprecated and no longer supported, use sceneIncrementO/sceneDecrementO instead")
|
|
}
|
|
|
|
if input.PlayCount != nil {
|
|
logger.Warnf("play_count is deprecated and no longer supported, use sceneIncrementPlayCount/sceneDecrementPlayCount instead")
|
|
}
|
|
|
|
updatedScene.PlayDuration = translator.optionalFloat64(input.PlayDuration, "play_duration")
|
|
updatedScene.Organized = translator.optionalBool(input.Organized, "organized")
|
|
updatedScene.StashIDs = translator.updateStashIDs(input.StashIds, "stash_ids")
|
|
|
|
var err error
|
|
|
|
updatedScene.Date, err = translator.optionalDate(input.Date, "date")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting date: %w", err)
|
|
}
|
|
updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
|
}
|
|
|
|
updatedScene.URLs = translator.optionalURLs(input.Urls, input.URL)
|
|
|
|
updatedScene.PrimaryFileID, err = translator.fileIDPtrFromString(input.PrimaryFileID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting primary file id: %w", err)
|
|
}
|
|
|
|
updatedScene.PerformerIDs, err = translator.updateIds(input.PerformerIds, "performer_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting performer ids: %w", err)
|
|
}
|
|
updatedScene.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
|
}
|
|
updatedScene.GalleryIDs, err = translator.updateIds(input.GalleryIds, "gallery_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
|
}
|
|
|
|
updatedScene.MovieIDs, err = translator.updateMovieIDs(input.Movies, "movies")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting movies: %w", err)
|
|
}
|
|
|
|
return &updatedScene, nil
|
|
}
|
|
|
|
func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUpdateInput, translator changesetTranslator) (*models.Scene, error) {
|
|
sceneID, err := strconv.Atoi(input.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
qb := r.repository.Scene
|
|
|
|
originalScene, err := qb.Find(ctx, sceneID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if originalScene == nil {
|
|
return nil, fmt.Errorf("scene with id %d not found", sceneID)
|
|
}
|
|
|
|
// Populate scene from the input
|
|
updatedScene, err := scenePartialFromInput(input, translator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// ensure that title is set where scene has no file
|
|
if updatedScene.Title.Set && updatedScene.Title.Value == "" {
|
|
if err := originalScene.LoadFiles(ctx, r.repository.Scene); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(originalScene.Files.List()) == 0 {
|
|
return nil, errors.New("title must be set if scene has no files")
|
|
}
|
|
}
|
|
|
|
if updatedScene.PrimaryFileID != nil {
|
|
newPrimaryFileID := *updatedScene.PrimaryFileID
|
|
|
|
// if file hash has changed, we should migrate generated files
|
|
// after commit
|
|
if err := originalScene.LoadFiles(ctx, r.repository.Scene); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// ensure that new primary file is associated with scene
|
|
var f *models.VideoFile
|
|
for _, ff := range originalScene.Files.List() {
|
|
if ff.ID == newPrimaryFileID {
|
|
f = ff
|
|
}
|
|
}
|
|
|
|
if f == nil {
|
|
return nil, fmt.Errorf("file with id %d not associated with scene", newPrimaryFileID)
|
|
}
|
|
}
|
|
|
|
var coverImageData []byte
|
|
if input.CoverImage != nil {
|
|
var err error
|
|
coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing cover image: %w", err)
|
|
}
|
|
}
|
|
|
|
scene, err := qb.UpdatePartial(ctx, sceneID, *updatedScene)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := r.sceneUpdateCoverImage(ctx, scene, coverImageData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return scene, nil
|
|
}
|
|
|
|
func (r *mutationResolver) sceneUpdateCoverImage(ctx context.Context, s *models.Scene, coverImageData []byte) error {
|
|
if len(coverImageData) > 0 {
|
|
qb := r.repository.Scene
|
|
|
|
// update cover table
|
|
if err := qb.UpdateCover(ctx, s.ID, coverImageData); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneUpdateInput) ([]*models.Scene, error) {
|
|
sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting ids: %w", err)
|
|
}
|
|
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
// Populate scene from the input
|
|
updatedScene := models.NewScenePartial()
|
|
|
|
updatedScene.Title = translator.optionalString(input.Title, "title")
|
|
updatedScene.Code = translator.optionalString(input.Code, "code")
|
|
updatedScene.Details = translator.optionalString(input.Details, "details")
|
|
updatedScene.Director = translator.optionalString(input.Director, "director")
|
|
updatedScene.Rating = translator.optionalInt(input.Rating100, "rating100")
|
|
updatedScene.Organized = translator.optionalBool(input.Organized, "organized")
|
|
|
|
updatedScene.Date, err = translator.optionalDate(input.Date, "date")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting date: %w", err)
|
|
}
|
|
updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
|
}
|
|
|
|
updatedScene.URLs = translator.optionalURLsBulk(input.Urls, input.URL)
|
|
|
|
updatedScene.PerformerIDs, err = translator.updateIdsBulk(input.PerformerIds, "performer_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting performer ids: %w", err)
|
|
}
|
|
updatedScene.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
|
}
|
|
updatedScene.GalleryIDs, err = translator.updateIdsBulk(input.GalleryIds, "gallery_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
|
}
|
|
|
|
updatedScene.MovieIDs, err = translator.updateMovieIDsBulk(input.MovieIds, "movie_ids")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting movie ids: %w", err)
|
|
}
|
|
|
|
ret := []*models.Scene{}
|
|
|
|
// Start the transaction and save the scenes
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
for _, sceneID := range sceneIDs {
|
|
scene, err := qb.UpdatePartial(ctx, sceneID, updatedScene)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ret = append(ret, scene)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// execute post hooks outside of txn
|
|
var newRet []*models.Scene
|
|
for _, scene := range ret {
|
|
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, hook.SceneUpdatePost, input, translator.getFields())
|
|
|
|
scene, err = r.getScene(ctx, scene.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newRet = append(newRet, scene)
|
|
}
|
|
|
|
return newRet, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) {
|
|
sceneID, err := strconv.Atoi(input.ID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
|
|
|
var s *models.Scene
|
|
fileDeleter := &scene.FileDeleter{
|
|
Deleter: file.NewDeleter(),
|
|
FileNamingAlgo: fileNamingAlgo,
|
|
Paths: manager.GetInstance().Paths,
|
|
}
|
|
|
|
deleteGenerated := utils.IsTrue(input.DeleteGenerated)
|
|
deleteFile := utils.IsTrue(input.DeleteFile)
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
var err error
|
|
s, err = qb.Find(ctx, sceneID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if s == nil {
|
|
return fmt.Errorf("scene with id %d not found", sceneID)
|
|
}
|
|
|
|
// kill any running encoders
|
|
manager.KillRunningStreams(s, fileNamingAlgo)
|
|
|
|
return r.sceneService.Destroy(ctx, s, fileDeleter, deleteGenerated, deleteFile)
|
|
}); err != nil {
|
|
fileDeleter.Rollback()
|
|
return false, err
|
|
}
|
|
|
|
// perform the post-commit actions
|
|
fileDeleter.Commit()
|
|
|
|
// call post hook after performing the other actions
|
|
r.hookExecutor.ExecutePostHooks(ctx, s.ID, hook.SceneDestroyPost, plugin.SceneDestroyInput{
|
|
SceneDestroyInput: input,
|
|
Checksum: s.Checksum,
|
|
OSHash: s.OSHash,
|
|
Path: s.Path,
|
|
}, nil)
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.ScenesDestroyInput) (bool, error) {
|
|
sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting ids: %w", err)
|
|
}
|
|
|
|
var scenes []*models.Scene
|
|
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
|
|
|
fileDeleter := &scene.FileDeleter{
|
|
Deleter: file.NewDeleter(),
|
|
FileNamingAlgo: fileNamingAlgo,
|
|
Paths: manager.GetInstance().Paths,
|
|
}
|
|
|
|
deleteGenerated := utils.IsTrue(input.DeleteGenerated)
|
|
deleteFile := utils.IsTrue(input.DeleteFile)
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
for _, id := range sceneIDs {
|
|
scene, err := qb.Find(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if scene == nil {
|
|
return fmt.Errorf("scene with id %d not found", id)
|
|
}
|
|
|
|
scenes = append(scenes, scene)
|
|
|
|
// kill any running encoders
|
|
manager.KillRunningStreams(scene, fileNamingAlgo)
|
|
|
|
if err := r.sceneService.Destroy(ctx, scene, fileDeleter, deleteGenerated, deleteFile); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
fileDeleter.Rollback()
|
|
return false, err
|
|
}
|
|
|
|
// perform the post-commit actions
|
|
fileDeleter.Commit()
|
|
|
|
for _, scene := range scenes {
|
|
// call post hook after performing the other actions
|
|
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, hook.SceneDestroyPost, plugin.ScenesDestroyInput{
|
|
ScenesDestroyInput: input,
|
|
Checksum: scene.Checksum,
|
|
OSHash: scene.OSHash,
|
|
Path: scene.Path,
|
|
}, nil)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneAssignFile(ctx context.Context, input AssignSceneFileInput) (bool, error) {
|
|
sceneID, err := strconv.Atoi(input.SceneID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting scene id: %w", err)
|
|
}
|
|
|
|
fileID, err := strconv.Atoi(input.FileID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting file id: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
return r.Resolver.sceneService.AssignFile(ctx, sceneID, models.FileID(fileID))
|
|
}); err != nil {
|
|
return false, fmt.Errorf("assigning file to scene: %w", err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneMerge(ctx context.Context, input SceneMergeInput) (*models.Scene, error) {
|
|
srcIDs, err := stringslice.StringSliceToIntSlice(input.Source)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting source ids: %w", err)
|
|
}
|
|
|
|
destID, err := strconv.Atoi(input.Destination)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting destination id: %w", err)
|
|
}
|
|
|
|
var values *models.ScenePartial
|
|
var coverImageData []byte
|
|
|
|
if input.Values != nil {
|
|
translator := changesetTranslator{
|
|
inputMap: getNamedUpdateInputMap(ctx, "input.values"),
|
|
}
|
|
|
|
values, err = scenePartialFromInput(*input.Values, translator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if input.Values.CoverImage != nil {
|
|
var err error
|
|
coverImageData, err = utils.ProcessImageInput(ctx, *input.Values.CoverImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing cover image: %w", err)
|
|
}
|
|
}
|
|
} else {
|
|
v := models.NewScenePartial()
|
|
values = &v
|
|
}
|
|
|
|
mgr := manager.GetInstance()
|
|
fileDeleter := &scene.FileDeleter{
|
|
Deleter: file.NewDeleter(),
|
|
FileNamingAlgo: mgr.Config.GetVideoFileNamingAlgorithm(),
|
|
Paths: mgr.Paths,
|
|
}
|
|
|
|
var ret *models.Scene
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
if err := r.Resolver.sceneService.Merge(ctx, srcIDs, destID, fileDeleter, scene.MergeOptions{
|
|
ScenePartial: *values,
|
|
IncludePlayHistory: utils.IsTrue(input.PlayHistory),
|
|
IncludeOHistory: utils.IsTrue(input.OHistory),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
ret, err = r.Resolver.repository.Scene.Find(ctx, destID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret == nil {
|
|
return fmt.Errorf("scene with id %d not found", destID)
|
|
}
|
|
|
|
return r.sceneUpdateCoverImage(ctx, ret, coverImageData)
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (r *mutationResolver) getSceneMarker(ctx context.Context, id int) (ret *models.SceneMarker, err error) {
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
ret, err = r.repository.SceneMarker.Find(ctx, id)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input SceneMarkerCreateInput) (*models.SceneMarker, error) {
|
|
sceneID, err := strconv.Atoi(input.SceneID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting scene id: %w", err)
|
|
}
|
|
|
|
primaryTagID, err := strconv.Atoi(input.PrimaryTagID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting primary tag id: %w", err)
|
|
}
|
|
|
|
// Populate a new scene marker from the input
|
|
newMarker := models.NewSceneMarker()
|
|
|
|
newMarker.Title = input.Title
|
|
newMarker.Seconds = input.Seconds
|
|
newMarker.PrimaryTagID = primaryTagID
|
|
newMarker.SceneID = sceneID
|
|
|
|
tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.SceneMarker
|
|
|
|
err := qb.Create(ctx, &newMarker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save the marker tags
|
|
// If this tag is the primary tag, then let's not add it.
|
|
tagIDs = sliceutil.Exclude(tagIDs, []int{newMarker.PrimaryTagID})
|
|
return qb.UpdateTags(ctx, newMarker.ID, tagIDs)
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r.hookExecutor.ExecutePostHooks(ctx, newMarker.ID, hook.SceneMarkerCreatePost, input, nil)
|
|
return r.getSceneMarker(ctx, newMarker.ID)
|
|
}
|
|
|
|
func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMarkerUpdateInput) (*models.SceneMarker, error) {
|
|
markerID, err := strconv.Atoi(input.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
// Populate scene marker from the input
|
|
updatedMarker := models.NewSceneMarkerPartial()
|
|
|
|
updatedMarker.Title = translator.optionalString(input.Title, "title")
|
|
updatedMarker.Seconds = translator.optionalFloat64(input.Seconds, "seconds")
|
|
updatedMarker.SceneID, err = translator.optionalIntFromString(input.SceneID, "scene_id")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting scene id: %w", err)
|
|
}
|
|
updatedMarker.PrimaryTagID, err = translator.optionalIntFromString(input.PrimaryTagID, "primary_tag_id")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting primary tag id: %w", err)
|
|
}
|
|
|
|
var tagIDs []int
|
|
tagIdsIncluded := translator.hasField("tag_ids")
|
|
if input.TagIds != nil {
|
|
tagIDs, err = stringslice.StringSliceToIntSlice(input.TagIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
|
}
|
|
}
|
|
|
|
mgr := manager.GetInstance()
|
|
|
|
fileDeleter := &scene.FileDeleter{
|
|
Deleter: file.NewDeleter(),
|
|
FileNamingAlgo: mgr.Config.GetVideoFileNamingAlgorithm(),
|
|
Paths: mgr.Paths,
|
|
}
|
|
|
|
// Start the transaction and save the scene marker
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.SceneMarker
|
|
sqb := r.repository.Scene
|
|
|
|
// check to see if timestamp was changed
|
|
existingMarker, err := qb.Find(ctx, markerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existingMarker == nil {
|
|
return fmt.Errorf("scene marker with id %d not found", markerID)
|
|
}
|
|
|
|
newMarker, err := qb.UpdatePartial(ctx, markerID, updatedMarker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
existingScene, err := sqb.Find(ctx, existingMarker.SceneID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existingScene == nil {
|
|
return fmt.Errorf("scene with id %d not found", existingMarker.SceneID)
|
|
}
|
|
|
|
// remove the marker preview if the scene changed or if the timestamp was changed
|
|
if existingMarker.SceneID != newMarker.SceneID || existingMarker.Seconds != newMarker.Seconds {
|
|
seconds := int(existingMarker.Seconds)
|
|
if err := fileDeleter.MarkMarkerFiles(existingScene, seconds); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if tagIdsIncluded {
|
|
// Save the marker tags
|
|
// If this tag is the primary tag, then let's not add it.
|
|
tagIDs = sliceutil.Exclude(tagIDs, []int{newMarker.PrimaryTagID})
|
|
if err := qb.UpdateTags(ctx, markerID, tagIDs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
fileDeleter.Rollback()
|
|
return nil, err
|
|
}
|
|
|
|
// perform the post-commit actions
|
|
fileDeleter.Commit()
|
|
|
|
r.hookExecutor.ExecutePostHooks(ctx, markerID, hook.SceneMarkerUpdatePost, input, translator.getFields())
|
|
return r.getSceneMarker(ctx, markerID)
|
|
}
|
|
|
|
func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) {
|
|
markerID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
|
|
|
fileDeleter := &scene.FileDeleter{
|
|
Deleter: file.NewDeleter(),
|
|
FileNamingAlgo: fileNamingAlgo,
|
|
Paths: manager.GetInstance().Paths,
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.SceneMarker
|
|
sqb := r.repository.Scene
|
|
|
|
marker, err := qb.Find(ctx, markerID)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if marker == nil {
|
|
return fmt.Errorf("scene marker with id %d not found", markerID)
|
|
}
|
|
|
|
s, err := sqb.Find(ctx, marker.SceneID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if s == nil {
|
|
return fmt.Errorf("scene with id %d not found", marker.SceneID)
|
|
}
|
|
|
|
return scene.DestroyMarker(ctx, s, marker, qb, fileDeleter)
|
|
}); err != nil {
|
|
fileDeleter.Rollback()
|
|
return false, err
|
|
}
|
|
|
|
// perform the post-commit actions
|
|
fileDeleter.Commit()
|
|
|
|
r.hookExecutor.ExecutePostHooks(ctx, markerID, hook.SceneMarkerDestroyPost, id, nil)
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneSaveActivity(ctx context.Context, id string, resumeTime *float64, playDuration *float64) (ret bool, err error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
ret, err = qb.SaveActivity(ctx, sceneID, resumeTime, playDuration)
|
|
return err
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// deprecated
|
|
func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id string) (ret int, err error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.AddViews(ctx, sceneID, nil)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(updatedTimes), nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneAddPlay(ctx context.Context, id string, t []*time.Time) (*HistoryMutationResult, error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
var times []time.Time
|
|
|
|
// convert time to local time, so that sorting is consistent
|
|
for _, tt := range t {
|
|
times = append(times, tt.Local())
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.AddViews(ctx, sceneID, times)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HistoryMutationResult{
|
|
Count: len(updatedTimes),
|
|
History: sliceutil.ValuesToPtrs(updatedTimes),
|
|
}, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneDeletePlay(ctx context.Context, id string, t []*time.Time) (*HistoryMutationResult, error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var times []time.Time
|
|
|
|
for _, tt := range t {
|
|
times = append(times, *tt)
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.DeleteViews(ctx, sceneID, times)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HistoryMutationResult{
|
|
Count: len(updatedTimes),
|
|
History: sliceutil.ValuesToPtrs(updatedTimes),
|
|
}, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneResetPlayCount(ctx context.Context, id string) (ret int, err error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
ret, err = qb.DeleteAllViews(ctx, sceneID)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// deprecated
|
|
func (r *mutationResolver) SceneIncrementO(ctx context.Context, id string) (ret int, err error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.AddO(ctx, sceneID, nil)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(updatedTimes), nil
|
|
}
|
|
|
|
// deprecated
|
|
func (r *mutationResolver) SceneDecrementO(ctx context.Context, id string) (ret int, err error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.DeleteO(ctx, sceneID, nil)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(updatedTimes), nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneResetO(ctx context.Context, id string) (ret int, err error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
ret, err = qb.ResetO(ctx, sceneID)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneAddO(ctx context.Context, id string, t []*time.Time) (*HistoryMutationResult, error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
var times []time.Time
|
|
|
|
// convert time to local time, so that sorting is consistent
|
|
for _, tt := range t {
|
|
times = append(times, tt.Local())
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.AddO(ctx, sceneID, times)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HistoryMutationResult{
|
|
Count: len(updatedTimes),
|
|
History: sliceutil.ValuesToPtrs(updatedTimes),
|
|
}, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneDeleteO(ctx context.Context, id string, t []*time.Time) (*HistoryMutationResult, error) {
|
|
sceneID, err := strconv.Atoi(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
var times []time.Time
|
|
|
|
for _, tt := range t {
|
|
times = append(times, *tt)
|
|
}
|
|
|
|
var updatedTimes []time.Time
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Scene
|
|
|
|
updatedTimes, err = qb.DeleteO(ctx, sceneID, times)
|
|
return err
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HistoryMutationResult{
|
|
Count: len(updatedTimes),
|
|
History: sliceutil.ValuesToPtrs(updatedTimes),
|
|
}, nil
|
|
}
|
|
|
|
func (r *mutationResolver) SceneGenerateScreenshot(ctx context.Context, id string, at *float64) (string, error) {
|
|
if at != nil {
|
|
manager.GetInstance().GenerateScreenshot(ctx, id, *at)
|
|
} else {
|
|
manager.GetInstance().GenerateDefaultScreenshot(ctx, id)
|
|
}
|
|
|
|
return "todo", nil
|
|
}
|