Refactor file deletion (#1954)

* Add file deleter
* Change scene delete code
* Add image/gallery delete code
* Don't remove stash library paths
* Fail silently if file does not exist
This commit is contained in:
WithoutPants
2021-11-29 14:08:32 +11:00
committed by GitHub
parent 17aa17fccc
commit 9ebf8331ac
11 changed files with 588 additions and 352 deletions

View File

@@ -5,9 +5,12 @@ import (
"database/sql"
"errors"
"fmt"
"os"
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
@@ -395,8 +398,14 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
}
var galleries []*models.Gallery
var imgsToPostProcess []*models.Image
var imgsToDelete []*models.Image
var imgsDestroyed []*models.Image
fileDeleter := &image.FileDeleter{
Deleter: *file.NewDeleter(),
Paths: manager.GetInstance().Paths,
}
deleteGenerated := utils.IsTrue(input.DeleteGenerated)
deleteFile := utils.IsTrue(input.DeleteFile)
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
@@ -422,13 +431,19 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
}
for _, img := range imgs {
if err := iqb.Destroy(img.ID); err != nil {
if err := image.Destroy(img, iqb, fileDeleter, deleteGenerated, false); err != nil {
return err
}
imgsToPostProcess = append(imgsToPostProcess, img)
imgsDestroyed = append(imgsDestroyed, img)
}
} else if input.DeleteFile != nil && *input.DeleteFile {
if deleteFile {
if err := fileDeleter.Files([]string{gallery.Path.String}); err != nil {
return err
}
}
} else if deleteFile {
// Delete image if it is only attached to this gallery
imgs, err := iqb.FindByGalleryID(id)
if err != nil {
@@ -442,14 +457,16 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
}
if len(imgGalleries) == 1 {
if err := iqb.Destroy(img.ID); err != nil {
if err := image.Destroy(img, iqb, fileDeleter, deleteGenerated, deleteFile); err != nil {
return err
}
imgsToDelete = append(imgsToDelete, img)
imgsToPostProcess = append(imgsToPostProcess, img)
imgsDestroyed = append(imgsDestroyed, img)
}
}
// we only want to delete a folder-based gallery if it is empty.
// don't do this with the file deleter
}
if err := qb.Destroy(id); err != nil {
@@ -459,28 +476,19 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
return nil
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
// #1804 - delete the image files first, since they must be removed
// before deleting a folder
for _, img := range imgsToDelete {
manager.DeleteImageFile(img)
}
// perform the post-commit actions
fileDeleter.Commit()
for _, gallery := range galleries {
manager.DeleteGalleryFile(gallery)
}
}
// if delete generated is true, then delete the generated files
// for the gallery
if input.DeleteGenerated != nil && *input.DeleteGenerated {
for _, img := range imgsToPostProcess {
manager.DeleteGeneratedImageFiles(img)
for _, gallery := range galleries {
// don't delete stash library paths
if utils.IsTrue(input.DeleteFile) && !gallery.Zip && gallery.Path.Valid && !isStashPath(gallery.Path.String) {
// try to remove the folder - it is possible that it is not empty
// so swallow the error if present
_ = os.Remove(gallery.Path.String)
}
}
@@ -490,13 +498,24 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
}
// call image destroy post hook as well
for _, img := range imgsToDelete {
for _, img := range imgsDestroyed {
r.hookExecutor.ExecutePostHooks(ctx, img.ID, plugin.ImageDestroyPost, nil, nil)
}
return true, nil
}
func isStashPath(path string) bool {
stashConfigs := manager.GetInstance().Config.GetStashPaths()
for _, config := range stashConfigs {
if path == config.Path {
return true
}
}
return false
}
func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.GalleryAddInput) (bool, error) {
galleryID, err := strconv.Atoi(input.GalleryID)
if err != nil {

View File

@@ -6,6 +6,8 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
@@ -281,38 +283,34 @@ func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageD
return false, err
}
var image *models.Image
var i *models.Image
fileDeleter := &image.FileDeleter{
Deleter: *file.NewDeleter(),
Paths: manager.GetInstance().Paths,
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
image, err = qb.Find(imageID)
i, err = qb.Find(imageID)
if err != nil {
return err
}
if image == nil {
if i == nil {
return fmt.Errorf("image with id %d not found", imageID)
}
return qb.Destroy(imageID)
return image.Destroy(i, qb, fileDeleter, utils.IsTrue(input.DeleteGenerated), utils.IsTrue(input.DeleteFile))
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// if delete generated is true, then delete the generated files
// for the image
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedImageFiles(image)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteImageFile(image)
}
// perform the post-commit actions
fileDeleter.Commit()
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, image.ID, plugin.ImageDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, i.ID, plugin.ImageDestroyPost, input, nil)
return true, nil
}
@@ -324,44 +322,41 @@ func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.Image
}
var images []*models.Image
fileDeleter := &image.FileDeleter{
Deleter: *file.NewDeleter(),
Paths: manager.GetInstance().Paths,
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
for _, imageID := range imageIDs {
image, err := qb.Find(imageID)
i, err := qb.Find(imageID)
if err != nil {
return err
}
if image == nil {
if i == nil {
return fmt.Errorf("image with id %d not found", imageID)
}
images = append(images, image)
if err := qb.Destroy(imageID); err != nil {
images = append(images, i)
if err := image.Destroy(i, qb, fileDeleter, utils.IsTrue(input.DeleteGenerated), utils.IsTrue(input.DeleteFile)); err != nil {
return err
}
}
return nil
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// perform the post-commit actions
fileDeleter.Commit()
for _, image := range images {
// if delete generated is true, then delete the generated files
// for the image
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedImageFiles(image)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteImageFile(image)
}
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, image.ID, plugin.ImageDestroyPost, input, nil)
}

View File

@@ -7,6 +7,7 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models"
@@ -456,94 +457,93 @@ func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneD
return false, err
}
var scene *models.Scene
var postCommitFunc func()
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(repo models.Repository) error {
qb := repo.Scene()
var err error
scene, err = qb.Find(sceneID)
s, err = qb.Find(sceneID)
if err != nil {
return err
}
if scene == nil {
if s == nil {
return fmt.Errorf("scene with id %d not found", sceneID)
}
postCommitFunc, err = manager.DestroyScene(scene, repo)
return err
// kill any running encoders
manager.KillRunningStreams(s, fileNamingAlgo)
return scene.Destroy(s, repo, fileDeleter, deleteGenerated, deleteFile)
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// perform the post-commit actions
postCommitFunc()
// if delete generated is true, then delete the generated files
// for the scene
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedSceneFiles(scene, config.GetInstance().GetVideoFileNamingAlgorithm())
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteSceneFile(scene)
}
fileDeleter.Commit()
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, plugin.SceneDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, s.ID, plugin.SceneDestroyPost, input, nil)
return true, nil
}
func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.ScenesDestroyInput) (bool, error) {
var scenes []*models.Scene
var postCommitFuncs []func()
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(repo models.Repository) error {
qb := repo.Scene()
for _, id := range input.Ids {
sceneID, _ := strconv.Atoi(id)
scene, err := qb.Find(sceneID)
s, err := qb.Find(sceneID)
if err != nil {
return err
}
if scene != nil {
scenes = append(scenes, scene)
}
f, err := manager.DestroyScene(scene, repo)
if err != nil {
return err
if s != nil {
scenes = append(scenes, s)
}
postCommitFuncs = append(postCommitFuncs, f)
// kill any running encoders
manager.KillRunningStreams(s, fileNamingAlgo)
if err := scene.Destroy(s, repo, fileDeleter, deleteGenerated, deleteFile); err != nil {
return err
}
}
return nil
}); err != nil {
fileDeleter.Rollback()
return false, err
}
for _, f := range postCommitFuncs {
f()
}
// perform the post-commit actions
fileDeleter.Commit()
fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm()
for _, scene := range scenes {
// if delete generated is true, then delete the generated files
// for the scene
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedSceneFiles(scene, fileNamingAlgo)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteSceneFile(scene)
}
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, plugin.SceneDestroyPost, input, nil)
}
@@ -646,7 +646,14 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
return false, err
}
var postCommitFunc func()
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
fileDeleter := &scene.FileDeleter{
Deleter: *file.NewDeleter(),
FileNamingAlgo: fileNamingAlgo,
Paths: manager.GetInstance().Paths,
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.SceneMarker()
sqb := repo.Scene()
@@ -661,18 +668,19 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
return fmt.Errorf("scene marker with id %d not found", markerID)
}
scene, err := sqb.Find(int(marker.SceneID.Int64))
s, err := sqb.Find(int(marker.SceneID.Int64))
if err != nil {
return err
}
postCommitFunc, err = manager.DestroySceneMarker(scene, marker, qb)
return err
return scene.DestroyMarker(s, marker, qb, fileDeleter)
}); err != nil {
fileDeleter.Rollback()
return false, err
}
postCommitFunc()
// perform the post-commit actions
fileDeleter.Commit()
r.hookExecutor.ExecutePostHooks(ctx, markerID, plugin.SceneMarkerDestroyPost, id, nil)
@@ -682,7 +690,15 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
func (r *mutationResolver) changeMarker(ctx context.Context, changeType int, changedMarker models.SceneMarker, tagIDs []int) (*models.SceneMarker, error) {
var existingMarker *models.SceneMarker
var sceneMarker *models.SceneMarker
var scene *models.Scene
var s *models.Scene
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
fileDeleter := &scene.FileDeleter{
Deleter: *file.NewDeleter(),
FileNamingAlgo: fileNamingAlgo,
Paths: manager.GetInstance().Paths,
}
// Start the transaction and save the scene marker
if err := r.withTxn(ctx, func(repo models.Repository) error {
@@ -704,26 +720,31 @@ func (r *mutationResolver) changeMarker(ctx context.Context, changeType int, cha
return err
}
scene, err = sqb.Find(int(existingMarker.SceneID.Int64))
s, err = sqb.Find(int(existingMarker.SceneID.Int64))
}
if err != nil {
return err
}
// remove the marker preview if the timestamp was changed
if s != nil && existingMarker != nil && existingMarker.Seconds != changedMarker.Seconds {
seconds := int(existingMarker.Seconds)
if err := fileDeleter.MarkMarkerFiles(s, seconds); err != nil {
return err
}
}
// Save the marker tags
// If this tag is the primary tag, then let's not add it.
tagIDs = utils.IntExclude(tagIDs, []int{changedMarker.PrimaryTagID})
return qb.UpdateTags(sceneMarker.ID, tagIDs)
}); err != nil {
fileDeleter.Rollback()
return nil, err
}
// remove the marker preview if the timestamp was changed
if scene != nil && existingMarker != nil && existingMarker.Seconds != changedMarker.Seconds {
seconds := int(existingMarker.Seconds)
manager.DeleteSceneMarkerFiles(scene, seconds, config.GetInstance().GetVideoFileNamingAlgorithm())
}
// perform the post-commit actions
fileDeleter.Commit()
return sceneMarker, nil
}