2021-11-29 03:08:32 +00:00
|
|
|
package image
|
|
|
|
|
|
|
|
import (
|
2022-05-19 07:49:32 +00:00
|
|
|
"context"
|
2024-09-05 01:27:31 +00:00
|
|
|
"fmt"
|
2022-05-19 07:49:32 +00:00
|
|
|
|
2021-11-29 03:08:32 +00:00
|
|
|
"github.com/stashapp/stash/pkg/file"
|
2022-03-17 00:33:59 +00:00
|
|
|
"github.com/stashapp/stash/pkg/fsutil"
|
2023-08-07 23:34:05 +00:00
|
|
|
"github.com/stashapp/stash/pkg/logger"
|
2021-11-29 03:08:32 +00:00
|
|
|
"github.com/stashapp/stash/pkg/models"
|
2022-03-17 00:33:59 +00:00
|
|
|
"github.com/stashapp/stash/pkg/models/paths"
|
2021-11-29 03:08:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// FileDeleter is an extension of file.Deleter that handles deletion of image files.
|
|
|
|
type FileDeleter struct {
|
2022-07-13 06:30:54 +00:00
|
|
|
*file.Deleter
|
2021-11-29 03:08:32 +00:00
|
|
|
|
|
|
|
Paths *paths.Paths
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarkGeneratedFiles marks for deletion the generated files for the provided image.
|
|
|
|
func (d *FileDeleter) MarkGeneratedFiles(image *models.Image) error {
|
2023-05-16 23:30:51 +00:00
|
|
|
var files []string
|
2022-09-01 07:54:34 +00:00
|
|
|
thumbPath := d.Paths.Generated.GetThumbnailPath(image.Checksum, models.DefaultGthumbWidth)
|
2022-03-17 00:33:59 +00:00
|
|
|
exists, _ := fsutil.FileExists(thumbPath)
|
2021-11-29 03:08:32 +00:00
|
|
|
if exists {
|
2023-05-16 23:30:51 +00:00
|
|
|
files = append(files, thumbPath)
|
|
|
|
}
|
|
|
|
prevPath := d.Paths.Generated.GetClipPreviewPath(image.Checksum, models.DefaultGthumbWidth)
|
|
|
|
exists, _ = fsutil.FileExists(prevPath)
|
|
|
|
if exists {
|
|
|
|
files = append(files, prevPath)
|
2021-11-29 03:08:32 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 23:30:51 +00:00
|
|
|
return d.Files(files)
|
2021-11-29 03:08:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy destroys an image, optionally marking the file and generated files for deletion.
|
2022-07-13 06:30:54 +00:00
|
|
|
func (s *Service) Destroy(ctx context.Context, i *models.Image, fileDeleter *FileDeleter, deleteGenerated, deleteFile bool) error {
|
2022-07-18 00:51:59 +00:00
|
|
|
return s.destroyImage(ctx, i, fileDeleter, deleteGenerated, deleteFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DestroyZipImages destroys all images in zip, optionally marking the files and generated files for deletion.
|
|
|
|
// Returns a slice of images that were destroyed.
|
2023-09-01 00:39:29 +00:00
|
|
|
func (s *Service) DestroyZipImages(ctx context.Context, zipFile models.File, fileDeleter *FileDeleter, deleteGenerated bool) ([]*models.Image, error) {
|
2022-07-18 00:51:59 +00:00
|
|
|
var imgsDestroyed []*models.Image
|
2024-09-05 01:27:31 +00:00
|
|
|
zipFileID := zipFile.Base().ID
|
2022-07-18 00:51:59 +00:00
|
|
|
|
2024-09-05 01:27:31 +00:00
|
|
|
imgs, err := s.Repository.FindByZipFileID(ctx, zipFileID)
|
2022-07-18 00:51:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
|
2022-07-18 00:51:59 +00:00
|
|
|
for _, img := range imgs {
|
2022-09-01 07:54:34 +00:00
|
|
|
if err := img.LoadFiles(ctx, s.Repository); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-09-05 01:27:31 +00:00
|
|
|
// #5048 - if the image has multiple files, we just want to remove the file in the zip file,
|
|
|
|
// not delete the image entirely
|
|
|
|
if len(img.Files.List()) > 1 {
|
|
|
|
for _, f := range img.Files.List() {
|
|
|
|
if f.Base().ZipFileID == nil || *f.Base().ZipFileID != zipFileID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Repository.RemoveFileID(ctx, img.ID, f.Base().ID); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to remove file from image: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't delete the image
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-07-18 00:51:59 +00:00
|
|
|
const deleteFileInZip = false
|
|
|
|
if err := s.destroyImage(ctx, img, fileDeleter, deleteGenerated, deleteFileInZip); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
imgsDestroyed = append(imgsDestroyed, img)
|
|
|
|
}
|
|
|
|
|
|
|
|
return imgsDestroyed, nil
|
|
|
|
}
|
|
|
|
|
2024-09-05 01:27:31 +00:00
|
|
|
// DestroyFolderImages destroys all images in a folder, optionally marking the files and generated files for deletion.
|
|
|
|
// It will not delete images that are attached to more than one gallery.
|
|
|
|
// Returns a slice of images that were destroyed.
|
|
|
|
func (s *Service) DestroyFolderImages(ctx context.Context, folderID models.FolderID, fileDeleter *FileDeleter, deleteGenerated, deleteFile bool) ([]*models.Image, error) {
|
|
|
|
var imgsDestroyed []*models.Image
|
|
|
|
|
|
|
|
// find images in this folder
|
|
|
|
imgs, err := s.Repository.FindByFolderID(ctx, folderID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, img := range imgs {
|
|
|
|
if err := img.LoadFiles(ctx, s.Repository); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// #5048 - if the image has multiple files, we just want to remove the file
|
|
|
|
// in the folder
|
|
|
|
if len(img.Files.List()) > 1 {
|
|
|
|
for _, f := range img.Files.List() {
|
|
|
|
if f.Base().ParentFolderID != folderID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Repository.RemoveFileID(ctx, img.ID, f.Base().ID); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to remove file from image: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// we still want to delete the file from the folder, if applicable
|
|
|
|
if deleteFile {
|
|
|
|
if err := file.Destroy(ctx, s.File, f, fileDeleter.Deleter, deleteFile); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to delete image file: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't delete the image
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := img.LoadGalleryIDs(ctx, s.Repository); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// only destroy images that are not attached to other galleries
|
|
|
|
if len(img.GalleryIDs.List()) > 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Destroy(ctx, img, fileDeleter, deleteGenerated, deleteFile); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
imgsDestroyed = append(imgsDestroyed, img)
|
|
|
|
}
|
|
|
|
|
|
|
|
return imgsDestroyed, nil
|
|
|
|
}
|
|
|
|
|
2022-07-18 00:51:59 +00:00
|
|
|
// Destroy destroys an image, optionally marking the file and generated files for deletion.
|
|
|
|
func (s *Service) destroyImage(ctx context.Context, i *models.Image, fileDeleter *FileDeleter, deleteGenerated, deleteFile bool) error {
|
|
|
|
if deleteFile {
|
|
|
|
if err := s.deleteFiles(ctx, i, fileDeleter); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-29 03:08:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if deleteGenerated {
|
|
|
|
if err := fileDeleter.MarkGeneratedFiles(i); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 06:30:54 +00:00
|
|
|
return s.Repository.Destroy(ctx, i.ID)
|
|
|
|
}
|
|
|
|
|
2022-07-18 00:51:59 +00:00
|
|
|
// deleteFiles deletes files for the image from the database and file system, if they are not in use by other images
|
|
|
|
func (s *Service) deleteFiles(ctx context.Context, i *models.Image, fileDeleter *FileDeleter) error {
|
2022-09-02 01:18:37 +00:00
|
|
|
if err := i.LoadFiles(ctx, s.Repository); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-09-01 07:54:34 +00:00
|
|
|
for _, f := range i.Files.List() {
|
2022-07-13 06:30:54 +00:00
|
|
|
// only delete files where there is no other associated image
|
2023-05-16 23:30:51 +00:00
|
|
|
otherImages, err := s.Repository.FindByFileID(ctx, f.Base().ID)
|
2022-07-13 06:30:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(otherImages) > 1 {
|
|
|
|
// other image associated, don't remove
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't delete files in zip archives
|
2022-07-18 00:51:59 +00:00
|
|
|
const deleteFile = true
|
2023-05-16 23:30:51 +00:00
|
|
|
if f.Base().ZipFileID == nil {
|
2023-08-07 23:34:05 +00:00
|
|
|
logger.Info("Deleting image file: ", f.Base().Path)
|
2022-07-13 06:30:54 +00:00
|
|
|
if err := file.Destroy(ctx, s.File, f, fileDeleter.Deleter, deleteFile); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2021-11-29 03:08:32 +00:00
|
|
|
}
|