Model refactor (#3915)

* Add mockery config file
* Move basic file/folder structs to models
* Fix hack due to import loop
* Move file interfaces to models
* Move folder interfaces to models
* Move scene interfaces to models
* Move scene marker interfaces to models
* Move image interfaces to models
* Move gallery interfaces to models
* Move gallery chapter interfaces to models
* Move studio interfaces to models
* Move movie interfaces to models
* Move performer interfaces to models
* Move tag interfaces to models
* Move autotag interfaces to models
* Regenerate mocks
This commit is contained in:
DingDongSoLong4
2023-09-01 02:39:29 +02:00
committed by GitHub
parent 20520a58b4
commit c364346a59
185 changed files with 3840 additions and 2559 deletions

View File

@@ -16,7 +16,6 @@ import (
"sync/atomic"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@@ -51,7 +50,7 @@ const (
type StreamType struct {
Name string
SegmentType *SegmentType
ServeManifest func(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string)
ServeManifest func(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *models.VideoFile, resolution string)
Args func(codec VideoCodec, segment int, videoFilter VideoFilter, videoOnly bool, outputDir string) Args
}
@@ -250,7 +249,7 @@ var ErrInvalidSegment = errors.New("invalid segment")
type StreamOptions struct {
StreamType *StreamType
VideoFile *file.VideoFile
VideoFile *models.VideoFile
Resolution string
Hash string
Segment string
@@ -279,7 +278,7 @@ type waitingSegment struct {
type runningStream struct {
dir string
streamType *StreamType
vf *file.VideoFile
vf *models.VideoFile
maxTranscodeSize int
outputDir string
@@ -394,7 +393,7 @@ func (tp *transcodeProcess) checkSegments() {
}
}
func lastSegment(vf *file.VideoFile) int {
func lastSegment(vf *models.VideoFile) int {
return int(math.Ceil(vf.Duration/segmentLength)) - 1
}
@@ -405,7 +404,7 @@ func segmentExists(path string) bool {
// serveHLSManifest serves a generated HLS playlist. The URLs for the segments
// are of the form {r.URL}/%d.ts{?urlQuery} where %d is the segment index.
func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string) {
func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *models.VideoFile, resolution string) {
if sm.cacheDir == "" {
logger.Error("[transcode] cannot live transcode with HLS because cache dir is unset")
http.Error(w, "cannot live transcode with HLS because cache dir is unset", http.StatusServiceUnavailable)
@@ -460,7 +459,7 @@ func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request,
}
// serveDASHManifest serves a generated DASH manifest.
func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string) {
func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *models.VideoFile, resolution string) {
if sm.cacheDir == "" {
logger.Error("[transcode] cannot live transcode with DASH because cache dir is unset")
http.Error(w, "cannot live transcode files with DASH because cache dir is unset", http.StatusServiceUnavailable)
@@ -550,7 +549,7 @@ func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request
utils.ServeStaticContent(w, r, buf.Bytes())
}
func (sm *StreamManager) ServeManifest(w http.ResponseWriter, r *http.Request, streamType *StreamType, vf *file.VideoFile, resolution string) {
func (sm *StreamManager) ServeManifest(w http.ResponseWriter, r *http.Request, streamType *StreamType, vf *models.VideoFile, resolution string) {
streamType.ServeManifest(sm, w, r, vf, resolution)
}

View File

@@ -8,7 +8,6 @@ import (
"strings"
"syscall"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@@ -134,7 +133,7 @@ var (
type TranscodeOptions struct {
StreamType StreamFormat
VideoFile *file.VideoFile
VideoFile *models.VideoFile
Resolution string
StartTime float64
}

View File

@@ -10,12 +10,13 @@ import (
"github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
// Cleaner scans through stored file and folder instances and removes those that are no longer present on disk.
type Cleaner struct {
FS FS
FS models.FS
Repository Repository
Handlers []CleanHandler
@@ -55,44 +56,44 @@ func (s *Cleaner) Clean(ctx context.Context, options CleanOptions, progress *job
}
type fileOrFolder struct {
fileID ID
folderID FolderID
fileID models.FileID
folderID models.FolderID
}
type deleteSet struct {
orderedList []fileOrFolder
fileIDSet map[ID]string
fileIDSet map[models.FileID]string
folderIDSet map[FolderID]string
folderIDSet map[models.FolderID]string
}
func newDeleteSet() deleteSet {
return deleteSet{
fileIDSet: make(map[ID]string),
folderIDSet: make(map[FolderID]string),
fileIDSet: make(map[models.FileID]string),
folderIDSet: make(map[models.FolderID]string),
}
}
func (s *deleteSet) add(id ID, path string) {
func (s *deleteSet) add(id models.FileID, path string) {
if _, ok := s.fileIDSet[id]; !ok {
s.orderedList = append(s.orderedList, fileOrFolder{fileID: id})
s.fileIDSet[id] = path
}
}
func (s *deleteSet) has(id ID) bool {
func (s *deleteSet) has(id models.FileID) bool {
_, ok := s.fileIDSet[id]
return ok
}
func (s *deleteSet) addFolder(id FolderID, path string) {
func (s *deleteSet) addFolder(id models.FolderID, path string) {
if _, ok := s.folderIDSet[id]; !ok {
s.orderedList = append(s.orderedList, fileOrFolder{folderID: id})
s.folderIDSet[id] = path
}
}
func (s *deleteSet) hasFolder(id FolderID) bool {
func (s *deleteSet) hasFolder(id models.FolderID) bool {
_, ok := s.folderIDSet[id]
return ok
}
@@ -113,7 +114,7 @@ func (j *cleanJob) execute(ctx context.Context) error {
if err := txn.WithReadTxn(ctx, j.Repository, func(ctx context.Context) error {
var err error
fileCount, err = j.Repository.CountAllInPaths(ctx, j.options.Paths)
fileCount, err = j.Repository.FileStore.CountAllInPaths(ctx, j.options.Paths)
if err != nil {
return err
}
@@ -177,7 +178,7 @@ func (j *cleanJob) assessFiles(ctx context.Context, toDelete *deleteSet) error {
return nil
}
files, err := j.Repository.FindAllInPaths(ctx, j.options.Paths, batchSize, offset)
files, err := j.Repository.FileStore.FindAllInPaths(ctx, j.options.Paths, batchSize, offset)
if err != nil {
return fmt.Errorf("error querying for files: %w", err)
}
@@ -221,9 +222,9 @@ func (j *cleanJob) assessFiles(ctx context.Context, toDelete *deleteSet) error {
}
// flagFolderForDelete adds folders to the toDelete set, with the leaf folders added first
func (j *cleanJob) flagFileForDelete(ctx context.Context, toDelete *deleteSet, f File) error {
func (j *cleanJob) flagFileForDelete(ctx context.Context, toDelete *deleteSet, f models.File) error {
// add contained files first
containedFiles, err := j.Repository.FindByZipFileID(ctx, f.Base().ID)
containedFiles, err := j.Repository.FileStore.FindByZipFileID(ctx, f.Base().ID)
if err != nil {
return fmt.Errorf("error finding contained files for %q: %w", f.Base().Path, err)
}
@@ -306,7 +307,7 @@ func (j *cleanJob) assessFolders(ctx context.Context, toDelete *deleteSet) error
return nil
}
func (j *cleanJob) flagFolderForDelete(ctx context.Context, toDelete *deleteSet, folder *Folder) error {
func (j *cleanJob) flagFolderForDelete(ctx context.Context, toDelete *deleteSet, folder *models.Folder) error {
// it is possible that child folders may be included while parent folders are not
// so we need to check child folders separately
toDelete.addFolder(folder.ID, folder.Path)
@@ -314,7 +315,7 @@ func (j *cleanJob) flagFolderForDelete(ctx context.Context, toDelete *deleteSet,
return nil
}
func (j *cleanJob) shouldClean(ctx context.Context, f File) bool {
func (j *cleanJob) shouldClean(ctx context.Context, f models.File) bool {
path := f.Base().Path
info, err := f.Base().Info(j.FS)
@@ -336,7 +337,7 @@ func (j *cleanJob) shouldClean(ctx context.Context, f File) bool {
return !filter.Accept(ctx, path, info)
}
func (j *cleanJob) shouldCleanFolder(ctx context.Context, f *Folder) bool {
func (j *cleanJob) shouldCleanFolder(ctx context.Context, f *models.Folder) bool {
path := f.Path
info, err := f.Info(j.FS)
@@ -376,7 +377,7 @@ func (j *cleanJob) shouldCleanFolder(ctx context.Context, f *Folder) bool {
return !filter.Accept(ctx, path, info)
}
func (j *cleanJob) deleteFile(ctx context.Context, fileID ID, fn string) {
func (j *cleanJob) deleteFile(ctx context.Context, fileID models.FileID, fn string) {
// delete associated objects
fileDeleter := NewDeleter()
if err := txn.WithTxn(ctx, j.Repository, func(ctx context.Context) error {
@@ -386,14 +387,14 @@ func (j *cleanJob) deleteFile(ctx context.Context, fileID ID, fn string) {
return err
}
return j.Repository.Destroy(ctx, fileID)
return j.Repository.FileStore.Destroy(ctx, fileID)
}); err != nil {
logger.Errorf("Error deleting file %q from database: %s", fn, err.Error())
return
}
}
func (j *cleanJob) deleteFolder(ctx context.Context, folderID FolderID, fn string) {
func (j *cleanJob) deleteFolder(ctx context.Context, folderID models.FolderID, fn string) {
// delete associated objects
fileDeleter := NewDeleter()
if err := txn.WithTxn(ctx, j.Repository, func(ctx context.Context) error {
@@ -410,7 +411,7 @@ func (j *cleanJob) deleteFolder(ctx context.Context, folderID FolderID, fn strin
}
}
func (j *cleanJob) fireHandlers(ctx context.Context, fileDeleter *Deleter, fileID ID) error {
func (j *cleanJob) fireHandlers(ctx context.Context, fileDeleter *Deleter, fileID models.FileID) error {
for _, h := range j.Handlers {
if err := h.HandleFile(ctx, fileDeleter, fileID); err != nil {
return err
@@ -420,7 +421,7 @@ func (j *cleanJob) fireHandlers(ctx context.Context, fileDeleter *Deleter, fileI
return nil
}
func (j *cleanJob) fireFolderHandlers(ctx context.Context, fileDeleter *Deleter, folderID FolderID) error {
func (j *cleanJob) fireFolderHandlers(ctx context.Context, fileDeleter *Deleter, folderID models.FolderID) error {
for _, h := range j.Handlers {
if err := h.HandleFolder(ctx, fileDeleter, folderID); err != nil {
return err

View File

@@ -9,6 +9,7 @@ import (
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
@@ -179,7 +180,7 @@ func (d *Deleter) renameForRestore(path string) error {
return d.RenamerRemover.Rename(path+deleteFileSuffix, path)
}
func Destroy(ctx context.Context, destroyer Destroyer, f File, fileDeleter *Deleter, deleteFile bool) error {
func Destroy(ctx context.Context, destroyer models.FileDestroyer, f models.File, fileDeleter *Deleter, deleteFile bool) error {
if err := destroyer.Destroy(ctx, f.Base().ID); err != nil {
return err
}
@@ -195,11 +196,11 @@ func Destroy(ctx context.Context, destroyer Destroyer, f File, fileDeleter *Dele
}
type ZipDestroyer struct {
FileDestroyer GetterDestroyer
FolderDestroyer FolderGetterDestroyer
FileDestroyer models.FileFinderDestroyer
FolderDestroyer models.FolderFinderDestroyer
}
func (d *ZipDestroyer) DestroyZip(ctx context.Context, f File, fileDeleter *Deleter, deleteFile bool) error {
func (d *ZipDestroyer) DestroyZip(ctx context.Context, f models.File, fileDeleter *Deleter, deleteFile bool) error {
// destroy contained files
files, err := d.FileDestroyer.FindByZipFileID(ctx, f.Base().ID)
if err != nil {

View File

@@ -1,225 +1,15 @@
package file
import (
"bytes"
"context"
"io"
"io/fs"
"net/http"
"strconv"
"time"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
// ID represents an ID of a file.
type ID int32
// Repository provides access to storage methods for files and folders.
type Repository struct {
txn.Manager
txn.DatabaseProvider
func (i ID) String() string {
return strconv.Itoa(int(i))
}
// DirEntry represents a file or directory in the file system.
type DirEntry struct {
ZipFileID *ID `json:"zip_file_id"`
// transient - not persisted
// only guaranteed to have id, path and basename set
ZipFile File
ModTime time.Time `json:"mod_time"`
}
func (e *DirEntry) info(fs FS, path string) (fs.FileInfo, error) {
if e.ZipFile != nil {
zipPath := e.ZipFile.Base().Path
zfs, err := fs.OpenZip(zipPath)
if err != nil {
return nil, err
}
defer zfs.Close()
fs = zfs
}
// else assume os file
ret, err := fs.Lstat(path)
return ret, err
}
// File represents a file in the file system.
type File interface {
Base() *BaseFile
SetFingerprints(fp Fingerprints)
Open(fs FS) (io.ReadCloser, error)
}
// BaseFile represents a file in the file system.
type BaseFile struct {
ID ID `json:"id"`
DirEntry
// resolved from parent folder and basename only - not stored in DB
Path string `json:"path"`
Basename string `json:"basename"`
ParentFolderID FolderID `json:"parent_folder_id"`
Fingerprints Fingerprints `json:"fingerprints"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// SetFingerprints sets the fingerprints of the file.
// If a fingerprint of the same type already exists, it is overwritten.
func (f *BaseFile) SetFingerprints(fp Fingerprints) {
for _, v := range fp {
f.SetFingerprint(v)
}
}
// SetFingerprint sets the fingerprint of the file.
// If a fingerprint of the same type already exists, it is overwritten.
func (f *BaseFile) SetFingerprint(fp Fingerprint) {
for i, existing := range f.Fingerprints {
if existing.Type == fp.Type {
f.Fingerprints[i] = fp
return
}
}
f.Fingerprints = append(f.Fingerprints, fp)
}
// Base is used to fulfil the File interface.
func (f *BaseFile) Base() *BaseFile {
return f
}
func (f *BaseFile) Open(fs FS) (io.ReadCloser, error) {
if f.ZipFile != nil {
zipPath := f.ZipFile.Base().Path
zfs, err := fs.OpenZip(zipPath)
if err != nil {
return nil, err
}
return zfs.OpenOnly(f.Path)
}
return fs.Open(f.Path)
}
func (f *BaseFile) Info(fs FS) (fs.FileInfo, error) {
return f.info(fs, f.Path)
}
func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) error {
reader, err := f.Open(fs)
if err != nil {
return err
}
defer reader.Close()
content, ok := reader.(io.ReadSeeker)
if !ok {
data, err := io.ReadAll(reader)
if err != nil {
return err
}
content = bytes.NewReader(data)
}
if r.URL.Query().Has("t") {
w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
} else {
w.Header().Set("Cache-Control", "no-cache")
}
http.ServeContent(w, r, f.Basename, f.ModTime, content)
return nil
}
type Finder interface {
Find(ctx context.Context, id ...ID) ([]File, error)
}
// Getter provides methods to find Files.
type Getter interface {
Finder
FindByPath(ctx context.Context, path string) (File, error)
FindAllByPath(ctx context.Context, path string) ([]File, error)
FindByFingerprint(ctx context.Context, fp Fingerprint) ([]File, error)
FindByZipFileID(ctx context.Context, zipFileID ID) ([]File, error)
FindAllInPaths(ctx context.Context, p []string, limit, offset int) ([]File, error)
FindByFileInfo(ctx context.Context, info fs.FileInfo, size int64) ([]File, error)
}
type Counter interface {
CountAllInPaths(ctx context.Context, p []string) (int, error)
CountByFolderID(ctx context.Context, folderID FolderID) (int, error)
}
// Creator provides methods to create Files.
type Creator interface {
Create(ctx context.Context, f File) error
}
// Updater provides methods to update Files.
type Updater interface {
Update(ctx context.Context, f File) error
}
type Destroyer interface {
Destroy(ctx context.Context, id ID) error
}
type GetterUpdater interface {
Getter
Updater
}
type GetterDestroyer interface {
Getter
Destroyer
}
// Store provides methods to find, create and update Files.
type Store interface {
Getter
Counter
Creator
Updater
Destroyer
IsPrimary(ctx context.Context, fileID ID) (bool, error)
}
// Decorator wraps the Decorate method to add additional functionality while scanning files.
type Decorator interface {
Decorate(ctx context.Context, fs FS, f File) (File, error)
IsMissingMetadata(ctx context.Context, fs FS, f File) bool
}
type FilteredDecorator struct {
Decorator
Filter
}
// Decorate runs the decorator if the filter accepts the file.
func (d *FilteredDecorator) Decorate(ctx context.Context, fs FS, f File) (File, error) {
if d.Accept(ctx, f) {
return d.Decorator.Decorate(ctx, fs, f)
}
return f, nil
}
func (d *FilteredDecorator) IsMissingMetadata(ctx context.Context, fs FS, f File) bool {
if d.Accept(ctx, f) {
return d.Decorator.IsMissingMetadata(ctx, fs, f)
}
return false
FileStore models.FileReaderWriter
FolderStore models.FolderReaderWriter
}

View File

@@ -3,94 +3,16 @@ package file
import (
"context"
"fmt"
"io/fs"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/stashapp/stash/pkg/models"
)
// FolderID represents an ID of a folder.
type FolderID int32
// String converts the ID to a string.
func (i FolderID) String() string {
return strconv.Itoa(int(i))
}
// Folder represents a folder in the file system.
type Folder struct {
ID FolderID `json:"id"`
DirEntry
Path string `json:"path"`
ParentFolderID *FolderID `json:"parent_folder_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (f *Folder) Info(fs FS) (fs.FileInfo, error) {
return f.info(fs, f.Path)
}
type FolderFinder interface {
Find(ctx context.Context, id FolderID) (*Folder, error)
}
// FolderPathFinder finds Folders by their path.
type FolderPathFinder interface {
FindByPath(ctx context.Context, path string) (*Folder, error)
}
// FolderGetter provides methods to find Folders.
type FolderGetter interface {
FolderFinder
FolderPathFinder
FindByZipFileID(ctx context.Context, zipFileID ID) ([]*Folder, error)
FindAllInPaths(ctx context.Context, p []string, limit, offset int) ([]*Folder, error)
FindByParentFolderID(ctx context.Context, parentFolderID FolderID) ([]*Folder, error)
}
type FolderCounter interface {
CountAllInPaths(ctx context.Context, p []string) (int, error)
}
// FolderCreator provides methods to create Folders.
type FolderCreator interface {
Create(ctx context.Context, f *Folder) error
}
type FolderFinderCreator interface {
FolderPathFinder
FolderCreator
}
// FolderUpdater provides methods to update Folders.
type FolderUpdater interface {
Update(ctx context.Context, f *Folder) error
}
type FolderDestroyer interface {
Destroy(ctx context.Context, id FolderID) error
}
type FolderGetterDestroyer interface {
FolderGetter
FolderDestroyer
}
// FolderStore provides methods to find, create and update Folders.
type FolderStore interface {
FolderGetter
FolderCounter
FolderCreator
FolderUpdater
FolderDestroyer
}
// GetOrCreateFolderHierarchy gets the folder for the given path, or creates a folder hierarchy for the given path if one if no existing folder is found.
// Does not create any folders in the file system
func GetOrCreateFolderHierarchy(ctx context.Context, fc FolderFinderCreator, path string) (*Folder, error) {
func GetOrCreateFolderHierarchy(ctx context.Context, fc models.FolderFinderCreator, path string) (*models.Folder, error) {
// get or create folder hierarchy
folder, err := fc.FindByPath(ctx, path)
if err != nil {
@@ -106,10 +28,10 @@ func GetOrCreateFolderHierarchy(ctx context.Context, fc FolderFinderCreator, pat
now := time.Now()
folder = &Folder{
folder = &models.Folder{
Path: path,
ParentFolderID: &parent.ID,
DirEntry: DirEntry{
DirEntry: models.DirEntry{
// leave mod time empty for now - it will be updated when the folder is scanned
},
CreatedAt: now,
@@ -126,7 +48,7 @@ func GetOrCreateFolderHierarchy(ctx context.Context, fc FolderFinderCreator, pat
// TransferZipFolderHierarchy creates the folder hierarchy for zipFileID under newPath, and removes
// ZipFileID from folders under oldPath.
func TransferZipFolderHierarchy(ctx context.Context, folderStore FolderStore, zipFileID ID, oldPath string, newPath string) error {
func TransferZipFolderHierarchy(ctx context.Context, folderStore models.FolderReaderWriter, zipFileID models.FileID, oldPath string, newPath string) error {
zipFolders, err := folderStore.FindByZipFileID(ctx, zipFileID)
if err != nil {
return err

View File

@@ -7,27 +7,28 @@ import (
"io/fs"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
type folderRenameCandidate struct {
folder *Folder
folder *models.Folder
found int
files int
}
type folderRenameDetector struct {
// candidates is a map of folder id to the number of files that match
candidates map[FolderID]folderRenameCandidate
candidates map[models.FolderID]folderRenameCandidate
// rejects is a set of folder ids which were found to still exist
rejects map[FolderID]struct{}
rejects map[models.FolderID]struct{}
}
func (d *folderRenameDetector) isReject(id FolderID) bool {
func (d *folderRenameDetector) isReject(id models.FolderID) bool {
_, ok := d.rejects[id]
return ok
}
func (d *folderRenameDetector) getCandidate(id FolderID) *folderRenameCandidate {
func (d *folderRenameDetector) getCandidate(id models.FolderID) *folderRenameCandidate {
c, ok := d.candidates[id]
if !ok {
return nil
@@ -40,14 +41,14 @@ func (d *folderRenameDetector) setCandidate(c folderRenameCandidate) {
d.candidates[c.folder.ID] = c
}
func (d *folderRenameDetector) reject(id FolderID) {
func (d *folderRenameDetector) reject(id models.FolderID) {
d.rejects[id] = struct{}{}
}
// bestCandidate returns the folder that is the best candidate for a rename.
// This is the folder that has the largest number of its original files that
// are still present in the new location.
func (d *folderRenameDetector) bestCandidate() *Folder {
func (d *folderRenameDetector) bestCandidate() *models.Folder {
if len(d.candidates) == 0 {
return nil
}
@@ -74,14 +75,14 @@ func (d *folderRenameDetector) bestCandidate() *Folder {
return best.folder
}
func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*Folder, error) {
func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*models.Folder, error) {
// in order for a folder to be considered moved, the existing folder must be
// missing, and the majority of the old folder's files must be present, unchanged,
// in the new folder.
detector := folderRenameDetector{
candidates: make(map[FolderID]folderRenameCandidate),
rejects: make(map[FolderID]struct{}),
candidates: make(map[models.FolderID]folderRenameCandidate),
rejects: make(map[models.FolderID]struct{}),
}
// rejects is a set of folder ids which were found to still exist
@@ -117,7 +118,7 @@ func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*Folder,
}
// check if the file exists in the database based on basename, size and mod time
existing, err := s.Repository.Store.FindByFileInfo(ctx, info, size)
existing, err := s.Repository.FileStore.FindByFileInfo(ctx, info, size)
if err != nil {
return fmt.Errorf("checking for existing file %q: %w", path, err)
}
@@ -163,7 +164,7 @@ func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*Folder,
// parent folder is missing, possible candidate
// count the total number of files in the existing folder
count, err := s.Repository.Store.CountByFolderID(ctx, parentFolderID)
count, err := s.Repository.FileStore.CountByFolderID(ctx, parentFolderID)
if err != nil {
return fmt.Errorf("counting files in folder %d: %w", parentFolderID, err)
}

View File

@@ -1,20 +0,0 @@
package file
// VisualFile is an interface for files that have a width and height.
type VisualFile interface {
File
GetWidth() int
GetHeight() int
GetFormat() string
}
func GetMinResolution(f VisualFile) int {
w := f.GetWidth()
h := f.GetHeight()
if w < h {
return w
}
return h
}

View File

@@ -6,6 +6,7 @@ import (
"os"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
)
// Opener provides an interface to open a file.
@@ -14,7 +15,7 @@ type Opener interface {
}
type fsOpener struct {
fs FS
fs models.FS
name string
}
@@ -22,15 +23,6 @@ func (o *fsOpener) Open() (io.ReadCloser, error) {
return o.fs.Open(o.name)
}
// FS represents a file system.
type FS interface {
Stat(name string) (fs.FileInfo, error)
Lstat(name string) (fs.FileInfo, error)
Open(name string) (fs.ReadDirFile, error)
OpenZip(name string) (*ZipFS, error)
IsPathCaseSensitive(path string) (bool, error)
}
// OsFS is a file system backed by the OS.
type OsFS struct{}
@@ -66,7 +58,7 @@ func (f *OsFS) Open(name string) (fs.ReadDirFile, error) {
return os.Open(name)
}
func (f *OsFS) OpenZip(name string) (*ZipFS, error) {
func (f *OsFS) OpenZip(name string) (models.ZipFS, error) {
info, err := f.Lstat(name)
if err != nil {
return nil, err

View File

@@ -3,6 +3,8 @@ package file
import (
"context"
"io/fs"
"github.com/stashapp/stash/pkg/models"
)
// PathFilter provides a filter function for paths.
@@ -18,18 +20,18 @@ func (pff PathFilterFunc) Accept(path string) bool {
// Filter provides a filter function for Files.
type Filter interface {
Accept(ctx context.Context, f File) bool
Accept(ctx context.Context, f models.File) bool
}
type FilterFunc func(ctx context.Context, f File) bool
type FilterFunc func(ctx context.Context, f models.File) bool
func (ff FilterFunc) Accept(ctx context.Context, f File) bool {
func (ff FilterFunc) Accept(ctx context.Context, f models.File) bool {
return ff(ctx, f)
}
// Handler provides a handler for Files.
type Handler interface {
Handle(ctx context.Context, f File, oldFile File) error
Handle(ctx context.Context, f models.File, oldFile models.File) error
}
// FilteredHandler is a Handler runs only if the filter accepts the file.
@@ -39,7 +41,7 @@ type FilteredHandler struct {
}
// Handle runs the handler if the filter accepts the file.
func (h *FilteredHandler) Handle(ctx context.Context, f File, oldFile File) error {
func (h *FilteredHandler) Handle(ctx context.Context, f models.File, oldFile models.File) error {
if h.Accept(ctx, f) {
return h.Handler.Handle(ctx, f, oldFile)
}
@@ -48,6 +50,6 @@ func (h *FilteredHandler) Handle(ctx context.Context, f File, oldFile File) erro
// CleanHandler provides a handler for cleaning Files and Folders.
type CleanHandler interface {
HandleFile(ctx context.Context, fileDeleter *Deleter, fileID ID) error
HandleFolder(ctx context.Context, fileDeleter *Deleter, folderID FolderID) error
HandleFile(ctx context.Context, fileDeleter *Deleter, fileID models.FileID) error
HandleFolder(ctx context.Context, fileDeleter *Deleter, folderID models.FolderID) error
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/file/video"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
_ "golang.org/x/image/webp"
)
@@ -21,10 +22,10 @@ type Decorator struct {
FFProbe ffmpeg.FFProbe
}
func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file.File, error) {
func (d *Decorator) Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
base := f.Base()
decorateFallback := func() (file.File, error) {
decorateFallback := func() (models.File, error) {
r, err := fs.Open(base.Path)
if err != nil {
return f, fmt.Errorf("reading image file %q: %w", base.Path, err)
@@ -35,7 +36,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
if err != nil {
return f, fmt.Errorf("decoding image file %q: %w", base.Path, err)
}
return &file.ImageFile{
return &models.ImageFile{
BaseFile: base,
Format: format,
Width: c.Width,
@@ -58,7 +59,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
// Fallback to catch non-animated avif images that FFProbe detects as video files
if probe.Bitrate == 0 && probe.VideoCodec == "av1" {
return &file.ImageFile{
return &models.ImageFile{
BaseFile: base,
Format: "avif",
Width: probe.Width,
@@ -78,7 +79,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
return videoFileDecorator.Decorate(ctx, fs, f)
}
return &file.ImageFile{
return &models.ImageFile{
BaseFile: base,
Format: probe.VideoCodec,
Width: probe.Width,
@@ -86,14 +87,14 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
}, nil
}
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs file.FS, f file.File) bool {
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool {
const (
unsetString = "unset"
unsetNumber = -1
)
imf, isImage := f.(*file.ImageFile)
vf, isVideo := f.(*file.VideoFile)
imf, isImage := f.(*models.ImageFile)
vf, isVideo := f.(*models.VideoFile)
switch {
case isImage:

View File

@@ -1,21 +0,0 @@
package file
// ImageFile is an extension of BaseFile to represent image files.
type ImageFile struct {
*BaseFile
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
}
func (f ImageFile) GetWidth() int {
return f.Width
}
func (f ImageFile) GetHeight() int {
return f.Height
}
func (f ImageFile) GetFormat() string {
return f.Format
}

253
pkg/file/import.go Normal file
View File

@@ -0,0 +1,253 @@
package file
import (
"context"
"errors"
"fmt"
"path/filepath"
"time"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
)
var ErrZipFileNotExist = errors.New("zip file does not exist")
type Importer struct {
ReaderWriter models.FileFinderCreator
FolderStore models.FolderFinderCreator
Input jsonschema.DirEntry
file models.File
folder *models.Folder
}
func (i *Importer) PreImport(ctx context.Context) error {
var err error
switch ff := i.Input.(type) {
case *jsonschema.BaseDirEntry:
i.folder, err = i.folderJSONToFolder(ctx, ff)
default:
i.file, err = i.fileJSONToFile(ctx, i.Input)
}
return err
}
func (i *Importer) folderJSONToFolder(ctx context.Context, baseJSON *jsonschema.BaseDirEntry) (*models.Folder, error) {
ret := models.Folder{
DirEntry: models.DirEntry{
ModTime: baseJSON.ModTime.GetTime(),
},
Path: baseJSON.Path,
CreatedAt: baseJSON.CreatedAt.GetTime(),
UpdatedAt: baseJSON.CreatedAt.GetTime(),
}
if err := i.populateZipFileID(ctx, &ret.DirEntry); err != nil {
return nil, err
}
// set parent folder id during the creation process
return &ret, nil
}
func (i *Importer) fileJSONToFile(ctx context.Context, fileJSON jsonschema.DirEntry) (models.File, error) {
switch ff := fileJSON.(type) {
case *jsonschema.VideoFile:
baseFile, err := i.baseFileJSONToBaseFile(ctx, ff.BaseFile)
if err != nil {
return nil, err
}
return &models.VideoFile{
BaseFile: baseFile,
Format: ff.Format,
Width: ff.Width,
Height: ff.Height,
Duration: ff.Duration,
VideoCodec: ff.VideoCodec,
AudioCodec: ff.AudioCodec,
FrameRate: ff.FrameRate,
BitRate: ff.BitRate,
Interactive: ff.Interactive,
InteractiveSpeed: ff.InteractiveSpeed,
}, nil
case *jsonschema.ImageFile:
baseFile, err := i.baseFileJSONToBaseFile(ctx, ff.BaseFile)
if err != nil {
return nil, err
}
return &models.ImageFile{
BaseFile: baseFile,
Format: ff.Format,
Width: ff.Width,
Height: ff.Height,
}, nil
case *jsonschema.BaseFile:
return i.baseFileJSONToBaseFile(ctx, ff)
}
return nil, fmt.Errorf("unknown file type")
}
func (i *Importer) baseFileJSONToBaseFile(ctx context.Context, baseJSON *jsonschema.BaseFile) (*models.BaseFile, error) {
baseFile := models.BaseFile{
DirEntry: models.DirEntry{
ModTime: baseJSON.ModTime.GetTime(),
},
Basename: filepath.Base(baseJSON.Path),
Size: baseJSON.Size,
CreatedAt: baseJSON.CreatedAt.GetTime(),
UpdatedAt: baseJSON.CreatedAt.GetTime(),
}
for _, fp := range baseJSON.Fingerprints {
baseFile.Fingerprints = append(baseFile.Fingerprints, models.Fingerprint{
Type: fp.Type,
Fingerprint: fp.Fingerprint,
})
}
if err := i.populateZipFileID(ctx, &baseFile.DirEntry); err != nil {
return nil, err
}
return &baseFile, nil
}
func (i *Importer) populateZipFileID(ctx context.Context, f *models.DirEntry) error {
zipFilePath := i.Input.DirEntry().ZipFile
if zipFilePath != "" {
zf, err := i.ReaderWriter.FindByPath(ctx, zipFilePath)
if err != nil {
return fmt.Errorf("error finding file by path %q: %v", zipFilePath, err)
}
if zf == nil {
return ErrZipFileNotExist
}
id := zf.Base().ID
f.ZipFileID = &id
}
return nil
}
func (i *Importer) PostImport(ctx context.Context, id int) error {
return nil
}
func (i *Importer) Name() string {
return i.Input.DirEntry().Path
}
func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
path := i.Input.DirEntry().Path
existing, err := i.ReaderWriter.FindByPath(ctx, path)
if err != nil {
return nil, err
}
if existing != nil {
id := int(existing.Base().ID)
return &id, nil
}
return nil, nil
}
func (i *Importer) createFolderHierarchy(ctx context.Context, p string) (*models.Folder, error) {
parentPath := filepath.Dir(p)
if parentPath == p {
// get or create this folder
return i.getOrCreateFolder(ctx, p, nil)
}
parent, err := i.createFolderHierarchy(ctx, parentPath)
if err != nil {
return nil, err
}
return i.getOrCreateFolder(ctx, p, parent)
}
func (i *Importer) getOrCreateFolder(ctx context.Context, path string, parent *models.Folder) (*models.Folder, error) {
folder, err := i.FolderStore.FindByPath(ctx, path)
if err != nil {
return nil, err
}
if folder != nil {
return folder, nil
}
now := time.Now()
folder = &models.Folder{
Path: path,
CreatedAt: now,
UpdatedAt: now,
}
if parent != nil {
folder.ZipFileID = parent.ZipFileID
folder.ParentFolderID = &parent.ID
}
if err := i.FolderStore.Create(ctx, folder); err != nil {
return nil, err
}
return folder, nil
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
// create folder hierarchy and set parent folder id
path := i.Input.DirEntry().Path
path = filepath.Dir(path)
folder, err := i.createFolderHierarchy(ctx, path)
if err != nil {
return nil, fmt.Errorf("creating folder hierarchy for %q: %w", path, err)
}
if i.folder != nil {
return i.createFolder(ctx, folder)
}
return i.createFile(ctx, folder)
}
func (i *Importer) createFile(ctx context.Context, parentFolder *models.Folder) (*int, error) {
if parentFolder != nil {
i.file.Base().ParentFolderID = parentFolder.ID
}
if err := i.ReaderWriter.Create(ctx, i.file); err != nil {
return nil, fmt.Errorf("error creating file: %w", err)
}
id := int(i.file.Base().ID)
return &id, nil
}
func (i *Importer) createFolder(ctx context.Context, parentFolder *models.Folder) (*int, error) {
if parentFolder != nil {
i.folder.ParentFolderID = &parentFolder.ID
}
if err := i.FolderStore.Create(ctx, i.folder); err != nil {
return nil, fmt.Errorf("error creating folder: %w", err)
}
id := int(i.folder.ID)
return &id, nil
}
func (i *Importer) Update(ctx context.Context, id int) error {
// update not supported
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
@@ -40,14 +41,14 @@ func (r folderCreatorStatRenamerImpl) Mkdir(name string, perm os.FileMode) error
type Mover struct {
Renamer DirMakerStatRenamer
Files GetterUpdater
Folders FolderStore
Files models.FileFinderUpdater
Folders models.FolderReaderWriter
moved map[string]string
foldersCreated []string
}
func NewMover(fileStore GetterUpdater, folderStore FolderStore) *Mover {
func NewMover(fileStore models.FileFinderUpdater, folderStore models.FolderReaderWriter) *Mover {
return &Mover{
Files: fileStore,
Folders: folderStore,
@@ -60,7 +61,7 @@ func NewMover(fileStore GetterUpdater, folderStore FolderStore) *Mover {
// Move moves the file to the given folder and basename. If basename is empty, then the existing basename is used.
// Assumes that the parent folder exists in the filesystem.
func (m *Mover) Move(ctx context.Context, f File, folder *Folder, basename string) error {
func (m *Mover) Move(ctx context.Context, f models.File, folder *models.Folder, basename string) error {
fBase := f.Base()
// don't allow moving files in zip files

View File

@@ -13,6 +13,7 @@ import (
"github.com/remeh/sizedwaitgroup"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -24,15 +25,6 @@ const (
maxRetries = -1
)
// Repository provides access to storage methods for files and folders.
type Repository struct {
txn.Manager
txn.DatabaseProvider
Store
FolderStore FolderStore
}
// Scanner scans files into the database.
//
// The scan process works using two goroutines. The first walks through the provided paths
@@ -59,7 +51,7 @@ type Repository struct {
// If the file is not a renamed file, then the decorators are fired and the file is created, then
// the applicable handlers are fired.
type Scanner struct {
FS FS
FS models.FS
Repository Repository
FingerprintCalculator FingerprintCalculator
@@ -67,6 +59,38 @@ type Scanner struct {
FileDecorators []Decorator
}
// FingerprintCalculator calculates a fingerprint for the provided file.
type FingerprintCalculator interface {
CalculateFingerprints(f *models.BaseFile, o Opener, useExisting bool) ([]models.Fingerprint, error)
}
// Decorator wraps the Decorate method to add additional functionality while scanning files.
type Decorator interface {
Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error)
IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool
}
type FilteredDecorator struct {
Decorator
Filter
}
// Decorate runs the decorator if the filter accepts the file.
func (d *FilteredDecorator) Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
if d.Accept(ctx, f) {
return d.Decorator.Decorate(ctx, fs, f)
}
return f, nil
}
func (d *FilteredDecorator) IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool {
if d.Accept(ctx, f) {
return d.Decorator.IsMissingMetadata(ctx, fs, f)
}
return false
}
// ProgressReporter is used to report progress of the scan.
type ProgressReporter interface {
AddTotal(total int)
@@ -129,8 +153,8 @@ func (s *Scanner) Scan(ctx context.Context, handlers []Handler, options ScanOpti
}
type scanFile struct {
*BaseFile
fs FS
*models.BaseFile
fs models.FS
info fs.FileInfo
}
@@ -198,7 +222,7 @@ func (s *scanJob) queueFiles(ctx context.Context, paths []string) error {
return err
}
func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs.WalkDirFunc {
func (s *scanJob) queueFileFunc(ctx context.Context, f models.FS, zipFile *scanFile) fs.WalkDirFunc {
return func(path string, d fs.DirEntry, err error) error {
if err != nil {
// don't let errors prevent scanning
@@ -229,8 +253,8 @@ func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs
}
ff := scanFile{
BaseFile: &BaseFile{
DirEntry: DirEntry{
BaseFile: &models.BaseFile{
DirEntry: models.DirEntry{
ModTime: modTime(info),
},
Path: path,
@@ -286,7 +310,7 @@ func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs
}
}
func getFileSize(f FS, path string, info fs.FileInfo) (int64, error) {
func getFileSize(f models.FS, path string, info fs.FileInfo) (int64, error) {
// #2196/#3042 - replace size with target size if file is a symlink
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
targetInfo, err := f.Stat(path)
@@ -408,10 +432,10 @@ func (s *scanJob) processQueueItem(ctx context.Context, f scanFile) {
})
}
func (s *scanJob) getFolderID(ctx context.Context, path string) (*FolderID, error) {
func (s *scanJob) getFolderID(ctx context.Context, path string) (*models.FolderID, error) {
// check the folder cache first
if f, ok := s.folderPathToID.Load(path); ok {
v := f.(FolderID)
v := f.(models.FolderID)
return &v, nil
}
@@ -428,7 +452,7 @@ func (s *scanJob) getFolderID(ctx context.Context, path string) (*FolderID, erro
return &ret.ID, nil
}
func (s *scanJob) getZipFileID(ctx context.Context, zipFile *scanFile) (*ID, error) {
func (s *scanJob) getZipFileID(ctx context.Context, zipFile *scanFile) (*models.FileID, error) {
if zipFile == nil {
return nil, nil
}
@@ -441,11 +465,11 @@ func (s *scanJob) getZipFileID(ctx context.Context, zipFile *scanFile) (*ID, err
// check the folder cache first
if f, ok := s.zipPathToID.Load(path); ok {
v := f.(ID)
v := f.(models.FileID)
return &v, nil
}
ret, err := s.Repository.FindByPath(ctx, path)
ret, err := s.Repository.FileStore.FindByPath(ctx, path)
if err != nil {
return nil, fmt.Errorf("getting zip file ID for %q: %w", path, err)
}
@@ -489,7 +513,7 @@ func (s *scanJob) handleFolder(ctx context.Context, file scanFile) error {
})
}
func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, error) {
func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*models.Folder, error) {
renamed, err := s.handleFolderRename(ctx, file)
if err != nil {
return nil, err
@@ -501,7 +525,7 @@ func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, erro
now := time.Now()
toCreate := &Folder{
toCreate := &models.Folder{
DirEntry: file.DirEntry,
Path: file.Path,
CreatedAt: now,
@@ -536,7 +560,7 @@ func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, erro
return toCreate, nil
}
func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*Folder, error) {
func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*models.Folder, error) {
// ignore folders in zip files
if file.ZipFileID != nil {
return nil, nil
@@ -572,7 +596,7 @@ func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*Folde
return renamedFrom, nil
}
func (s *scanJob) onExistingFolder(ctx context.Context, f scanFile, existing *Folder) (*Folder, error) {
func (s *scanJob) onExistingFolder(ctx context.Context, f scanFile, existing *models.Folder) (*models.Folder, error) {
update := false
// update if mod time is changed
@@ -613,12 +637,12 @@ func modTime(info fs.FileInfo) time.Time {
func (s *scanJob) handleFile(ctx context.Context, f scanFile) error {
defer s.incrementProgress(f)
var ff File
var ff models.File
// don't use a transaction to check if new or existing
if err := s.withDB(ctx, func(ctx context.Context) error {
// determine if file already exists in data store
var err error
ff, err = s.Repository.FindByPath(ctx, f.Path)
ff, err = s.Repository.FileStore.FindByPath(ctx, f.Path)
if err != nil {
return fmt.Errorf("checking for existing file %q: %w", f.Path, err)
}
@@ -661,7 +685,7 @@ func (s *scanJob) isZipFile(path string) bool {
return false
}
func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (models.File, error) {
now := time.Now()
baseFile := f.BaseFile
@@ -716,7 +740,7 @@ func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
// if not renamed, queue file for creation
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Create(ctx, file); err != nil {
if err := s.Repository.FileStore.Create(ctx, file); err != nil {
return fmt.Errorf("creating file %q: %w", path, err)
}
@@ -732,7 +756,7 @@ func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
return file, nil
}
func (s *scanJob) fireDecorators(ctx context.Context, fs FS, f File) (File, error) {
func (s *scanJob) fireDecorators(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
for _, h := range s.FileDecorators {
var err error
f, err = h.Decorate(ctx, fs, f)
@@ -744,7 +768,7 @@ func (s *scanJob) fireDecorators(ctx context.Context, fs FS, f File) (File, erro
return f, nil
}
func (s *scanJob) fireHandlers(ctx context.Context, f File, oldFile File) error {
func (s *scanJob) fireHandlers(ctx context.Context, f models.File, oldFile models.File) error {
for _, h := range s.handlers {
if err := h.Handle(ctx, f, oldFile); err != nil {
return err
@@ -754,7 +778,7 @@ func (s *scanJob) fireHandlers(ctx context.Context, f File, oldFile File) error
return nil
}
func (s *scanJob) calculateFingerprints(fs FS, f *BaseFile, path string, useExisting bool) (Fingerprints, error) {
func (s *scanJob) calculateFingerprints(fs models.FS, f *models.BaseFile, path string, useExisting bool) (models.Fingerprints, error) {
// only log if we're (re)calculating fingerprints
if !useExisting {
logger.Infof("Calculating fingerprints for %s ...", path)
@@ -772,7 +796,7 @@ func (s *scanJob) calculateFingerprints(fs FS, f *BaseFile, path string, useExis
return fp, nil
}
func appendFileUnique(v []File, toAdd []File) []File {
func appendFileUnique(v []models.File, toAdd []models.File) []models.File {
for _, f := range toAdd {
found := false
id := f.Base().ID
@@ -791,7 +815,7 @@ func appendFileUnique(v []File, toAdd []File) []File {
return v
}
func (s *scanJob) getFileFS(f *BaseFile) (FS, error) {
func (s *scanJob) getFileFS(f *models.BaseFile) (models.FS, error) {
if f.ZipFile == nil {
return s.FS, nil
}
@@ -805,11 +829,11 @@ func (s *scanJob) getFileFS(f *BaseFile) (FS, error) {
return fs.OpenZip(zipPath)
}
func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (File, error) {
var others []File
func (s *scanJob) handleRename(ctx context.Context, f models.File, fp []models.Fingerprint) (models.File, error) {
var others []models.File
for _, tfp := range fp {
thisOthers, err := s.Repository.FindByFingerprint(ctx, tfp)
thisOthers, err := s.Repository.FileStore.FindByFingerprint(ctx, tfp)
if err != nil {
return nil, fmt.Errorf("getting files by fingerprint %v: %w", tfp, err)
}
@@ -817,7 +841,7 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
others = appendFileUnique(others, thisOthers)
}
var missing []File
var missing []models.File
fZipID := f.Base().ZipFileID
for _, other := range others {
@@ -867,7 +891,7 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
fBase.Fingerprints = otherBase.Fingerprints
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, f); err != nil {
if err := s.Repository.FileStore.Update(ctx, f); err != nil {
return fmt.Errorf("updating file for rename %q: %w", fBase.Path, err)
}
@@ -889,7 +913,7 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
return f, nil
}
func (s *scanJob) isHandlerRequired(ctx context.Context, f File) bool {
func (s *scanJob) isHandlerRequired(ctx context.Context, f models.File) bool {
accept := len(s.options.HandlerRequiredFilters) == 0
for _, filter := range s.options.HandlerRequiredFilters {
// accept if any filter accepts the file
@@ -910,7 +934,7 @@ func (s *scanJob) isHandlerRequired(ctx context.Context, f File) bool {
// - file size
// - image format, width or height
// - video codec, audio codec, format, width, height, framerate or bitrate
func (s *scanJob) isMissingMetadata(ctx context.Context, f scanFile, existing File) bool {
func (s *scanJob) isMissingMetadata(ctx context.Context, f scanFile, existing models.File) bool {
for _, h := range s.FileDecorators {
if h.IsMissingMetadata(ctx, f.fs, existing) {
return true
@@ -920,7 +944,7 @@ func (s *scanJob) isMissingMetadata(ctx context.Context, f scanFile, existing Fi
return false
}
func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
path := existing.Base().Path
logger.Infof("Updating metadata for %s", path)
@@ -934,7 +958,7 @@ func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing F
// queue file for update
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, existing); err != nil {
if err := s.Repository.FileStore.Update(ctx, existing); err != nil {
return fmt.Errorf("updating file %q: %w", path, err)
}
@@ -946,7 +970,7 @@ func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing F
return existing, nil
}
func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
const useExisting = true
fp, err := s.calculateFingerprints(f.fs, existing.Base(), f.Path, useExisting)
if err != nil {
@@ -957,7 +981,7 @@ func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existi
existing.SetFingerprints(fp)
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, existing); err != nil {
if err := s.Repository.FileStore.Update(ctx, existing); err != nil {
return fmt.Errorf("updating file %q: %w", f.Path, err)
}
@@ -971,7 +995,7 @@ func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existi
}
// returns a file only if it was updated
func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
base := existing.Base()
path := base.Path
@@ -1006,7 +1030,7 @@ func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing File)
// queue file for update
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, existing); err != nil {
if err := s.Repository.FileStore.Update(ctx, existing); err != nil {
return fmt.Errorf("updating file %q: %w", path, err)
}
@@ -1022,21 +1046,21 @@ func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing File)
return existing, nil
}
func (s *scanJob) removeOutdatedFingerprints(existing File, fp Fingerprints) {
func (s *scanJob) removeOutdatedFingerprints(existing models.File, fp models.Fingerprints) {
// HACK - if no MD5 fingerprint was returned, and the oshash is changed
// then remove the MD5 fingerprint
oshash := fp.For(FingerprintTypeOshash)
oshash := fp.For(models.FingerprintTypeOshash)
if oshash == nil {
return
}
existingOshash := existing.Base().Fingerprints.For(FingerprintTypeOshash)
existingOshash := existing.Base().Fingerprints.For(models.FingerprintTypeOshash)
if existingOshash == nil || *existingOshash == *oshash {
// missing oshash or same oshash - nothing to do
return
}
md5 := fp.For(FingerprintTypeMD5)
md5 := fp.For(models.FingerprintTypeMD5)
if md5 != nil {
// nothing to do
@@ -1045,11 +1069,11 @@ func (s *scanJob) removeOutdatedFingerprints(existing File, fp Fingerprints) {
// oshash has changed, MD5 is missing - remove MD5 from the existing fingerprints
logger.Infof("Removing outdated checksum from %s", existing.Base().Path)
existing.Base().Fingerprints.Remove(FingerprintTypeMD5)
existing.Base().Fingerprints.Remove(models.FingerprintTypeMD5)
}
// returns a file only if it was updated
func (s *scanJob) onUnchangedFile(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) onUnchangedFile(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
var err error
isMissingMetdata := s.isMissingMetadata(ctx, f, existing)

View File

@@ -9,7 +9,6 @@ import (
"strings"
"github.com/asticode/go-astisub"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
@@ -87,12 +86,12 @@ func getCaptionsLangFromPath(captionPath string) string {
}
type CaptionUpdater interface {
GetCaptions(ctx context.Context, fileID file.ID) ([]*models.VideoCaption, error)
UpdateCaptions(ctx context.Context, fileID file.ID, captions []*models.VideoCaption) error
GetCaptions(ctx context.Context, fileID models.FileID) ([]*models.VideoCaption, error)
UpdateCaptions(ctx context.Context, fileID models.FileID, captions []*models.VideoCaption) error
}
// associates captions to scene/s with the same basename
func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manager, fqb file.Getter, w CaptionUpdater) {
func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manager, fqb models.FileFinder, w CaptionUpdater) {
captionLang := getCaptionsLangFromPath(captionPath)
captionPrefix := getCaptionPrefix(captionPath)
@@ -108,7 +107,7 @@ func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manag
// found some files
// filter out non video files
switch f.(type) {
case *file.VideoFile:
case *models.VideoFile:
break
default:
continue
@@ -143,7 +142,7 @@ func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manag
}
// CleanCaptions removes non existent/accessible language codes from captions
func CleanCaptions(ctx context.Context, f *file.VideoFile, txnMgr txn.Manager, w CaptionUpdater) error {
func CleanCaptions(ctx context.Context, f *models.VideoFile, txnMgr txn.Manager, w CaptionUpdater) error {
captions, err := w.GetCaptions(ctx, f.ID)
if err != nil {
return fmt.Errorf("getting captions for file %s: %w", f.Path, err)

View File

@@ -7,6 +7,7 @@ import (
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// Decorator adds video specific fields to a File.
@@ -14,7 +15,7 @@ type Decorator struct {
FFProbe ffmpeg.FFProbe
}
func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file.File, error) {
func (d *Decorator) Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
if d.FFProbe == "" {
return f, errors.New("ffprobe not configured")
}
@@ -42,7 +43,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
interactive = true
}
return &file.VideoFile{
return &models.VideoFile{
BaseFile: base,
Format: string(container),
VideoCodec: videoFile.VideoCodec,
@@ -56,13 +57,13 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
}, nil
}
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs file.FS, f file.File) bool {
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool {
const (
unsetString = "unset"
unsetNumber = -1
)
vf, ok := f.(*file.VideoFile)
vf, ok := f.(*models.VideoFile)
if !ok {
return true
}

View File

@@ -1,29 +0,0 @@
package file
// VideoFile is an extension of BaseFile to represent video files.
type VideoFile struct {
*BaseFile
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Duration float64 `json:"duration"`
VideoCodec string `json:"video_codec"`
AudioCodec string `json:"audio_codec"`
FrameRate float64 `json:"frame_rate"`
BitRate int64 `json:"bitrate"`
Interactive bool `json:"interactive"`
InteractiveSpeed *int `json:"interactive_speed"`
}
func (f VideoFile) GetWidth() int {
return f.Width
}
func (f VideoFile) GetHeight() int {
return f.Height
}
func (f VideoFile) GetFormat() string {
return f.Format
}

View File

@@ -6,6 +6,8 @@ import (
"os"
"path/filepath"
"sort"
"github.com/stashapp/stash/pkg/models"
)
// Modified from github.com/facebookgo/symwalk
@@ -48,7 +50,7 @@ import (
//
// Note that symwalk.Walk does not terminate if there are any non-terminating loops in
// the file structure.
func walkSym(f FS, filename string, linkDirname string, walkFn fs.WalkDirFunc) error {
func walkSym(f models.FS, filename string, linkDirname string, walkFn fs.WalkDirFunc) error {
symWalkFunc := func(path string, info fs.DirEntry, err error) error {
if fname, err := filepath.Rel(filename, path); err == nil {
@@ -80,7 +82,7 @@ func walkSym(f FS, filename string, linkDirname string, walkFn fs.WalkDirFunc) e
}
// symWalk extends filepath.Walk to also follow symlinks
func symWalk(fs FS, path string, walkFn fs.WalkDirFunc) error {
func symWalk(fs models.FS, path string, walkFn fs.WalkDirFunc) error {
return walkSym(fs, path, path, walkFn)
}
@@ -93,7 +95,7 @@ func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
func fsWalk(f FS, root string, fn fs.WalkDirFunc) error {
func fsWalk(f models.FS, root string, fn fs.WalkDirFunc) error {
info, err := f.Lstat(root)
if err != nil {
err = fn(root, nil, err)
@@ -106,7 +108,7 @@ func fsWalk(f FS, root string, fn fs.WalkDirFunc) error {
return err
}
func walkDir(f FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
func walkDir(f models.FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
if errors.Is(err, fs.SkipDir) && d.IsDir() {
// Successfully skipped directory.
@@ -143,7 +145,7 @@ func walkDir(f FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
// readDir reads the directory named by dirname and returns
// a sorted list of directory entries.
func readDir(fs FS, dirname string) ([]fs.DirEntry, error) {
func readDir(fs models.FS, dirname string) ([]fs.DirEntry, error) {
f, err := fs.Open(dirname)
if err != nil {
return nil, err

View File

@@ -10,6 +10,7 @@ import (
"path/filepath"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/xWTF/chardet"
"golang.org/x/net/html/charset"
@@ -22,14 +23,14 @@ var (
)
// ZipFS is a file system backed by a zip file.
type ZipFS struct {
type zipFS struct {
*zip.Reader
zipFileCloser io.Closer
zipInfo fs.FileInfo
zipPath string
}
func newZipFS(fs FS, path string, info fs.FileInfo) (*ZipFS, error) {
func newZipFS(fs models.FS, path string, info fs.FileInfo) (*zipFS, error) {
reader, err := fs.Open(path)
if err != nil {
return nil, err
@@ -85,7 +86,7 @@ func newZipFS(fs FS, path string, info fs.FileInfo) (*ZipFS, error) {
}
}
return &ZipFS{
return &zipFS{
Reader: zipReader,
zipFileCloser: reader,
zipInfo: info,
@@ -93,7 +94,7 @@ func newZipFS(fs FS, path string, info fs.FileInfo) (*ZipFS, error) {
}, nil
}
func (f *ZipFS) rel(name string) (string, error) {
func (f *zipFS) rel(name string) (string, error) {
if f.zipPath == name {
return ".", nil
}
@@ -110,7 +111,7 @@ func (f *ZipFS) rel(name string) (string, error) {
return relName, nil
}
func (f *ZipFS) Stat(name string) (fs.FileInfo, error) {
func (f *zipFS) Stat(name string) (fs.FileInfo, error) {
reader, err := f.Open(name)
if err != nil {
return nil, err
@@ -120,15 +121,15 @@ func (f *ZipFS) Stat(name string) (fs.FileInfo, error) {
return reader.Stat()
}
func (f *ZipFS) Lstat(name string) (fs.FileInfo, error) {
func (f *zipFS) Lstat(name string) (fs.FileInfo, error) {
return f.Stat(name)
}
func (f *ZipFS) OpenZip(name string) (*ZipFS, error) {
func (f *zipFS) OpenZip(name string) (models.ZipFS, error) {
return nil, errZipFSOpenZip
}
func (f *ZipFS) IsPathCaseSensitive(path string) (bool, error) {
func (f *zipFS) IsPathCaseSensitive(path string) (bool, error) {
return true, nil
}
@@ -145,7 +146,7 @@ func (f *zipReadDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
return asReadDirFile.ReadDir(n)
}
func (f *ZipFS) Open(name string) (fs.ReadDirFile, error) {
func (f *zipFS) Open(name string) (fs.ReadDirFile, error) {
relName, err := f.rel(name)
if err != nil {
return nil, err
@@ -161,12 +162,12 @@ func (f *ZipFS) Open(name string) (fs.ReadDirFile, error) {
}, nil
}
func (f *ZipFS) Close() error {
func (f *zipFS) Close() error {
return f.zipFileCloser.Close()
}
// openOnly returns a ReadCloser where calling Close will close the zip fs as well.
func (f *ZipFS) OpenOnly(name string) (io.ReadCloser, error) {
func (f *zipFS) OpenOnly(name string) (io.ReadCloser, error) {
r, err := f.Open(name)
if err != nil {
return nil, err

View File

@@ -8,15 +8,14 @@ import (
"github.com/stashapp/stash/pkg/models/jsonschema"
)
type ChapterCreatorUpdater interface {
Create(ctx context.Context, newGalleryChapter *models.GalleryChapter) error
Update(ctx context.Context, updatedGalleryChapter *models.GalleryChapter) error
type ChapterImporterReaderWriter interface {
models.GalleryChapterCreatorUpdater
FindByGalleryID(ctx context.Context, galleryID int) ([]*models.GalleryChapter, error)
}
type ChapterImporter struct {
GalleryID int
ReaderWriter ChapterCreatorUpdater
ReaderWriter ChapterImporterReaderWriter
Input jsonschema.GalleryChapter
MissingRefBehaviour models.ImportMissingRefEnum

View File

@@ -41,12 +41,7 @@ func (s *Service) Destroy(ctx context.Context, i *models.Gallery, fileDeleter *i
return imgsDestroyed, nil
}
type ChapterDestroyer interface {
FindByGalleryID(ctx context.Context, galleryID int) ([]*models.GalleryChapter, error)
Destroy(ctx context.Context, id int) error
}
func DestroyChapter(ctx context.Context, galleryChapter *models.GalleryChapter, qb ChapterDestroyer) error {
func DestroyChapter(ctx context.Context, galleryChapter *models.GalleryChapter, qb models.GalleryChapterDestroyer) error {
return qb.Destroy(ctx, galleryChapter.ID)
}

View File

@@ -7,13 +7,8 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
)
type ChapterFinder interface {
FindByGalleryID(ctx context.Context, galleryID int) ([]*models.GalleryChapter, error)
}
// ToBasicJSON converts a gallery object into its JSON object equivalent. It
// does not convert the relationships to other objects.
func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) {
@@ -48,7 +43,7 @@ func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) {
// GetStudioName returns the name of the provided gallery's studio. It returns an
// empty string if there is no studio assigned to the gallery.
func GetStudioName(ctx context.Context, reader studio.Finder, gallery *models.Gallery) (string, error) {
func GetStudioName(ctx context.Context, reader models.StudioGetter, gallery *models.Gallery) (string, error) {
if gallery.StudioID != nil {
studio, err := reader.Find(ctx, *gallery.StudioID)
if err != nil {
@@ -65,7 +60,7 @@ func GetStudioName(ctx context.Context, reader studio.Finder, gallery *models.Ga
// GetGalleryChaptersJSON returns a slice of GalleryChapter JSON representation
// objects corresponding to the provided gallery's chapters.
func GetGalleryChaptersJSON(ctx context.Context, chapterReader ChapterFinder, gallery *models.Gallery) ([]jsonschema.GalleryChapter, error) {
func GetGalleryChaptersJSON(ctx context.Context, chapterReader models.GalleryChapterFinder, gallery *models.Gallery) ([]jsonschema.GalleryChapter, error) {
galleryChapters, err := chapterReader.FindByGalleryID(ctx, gallery.ID)
if err != nil {
return nil, fmt.Errorf("error getting gallery chapters: %v", err)

View File

@@ -3,7 +3,6 @@ package gallery
import (
"errors"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@@ -50,8 +49,8 @@ var (
func createFullGallery(id int) models.Gallery {
return models.Gallery{
ID: id,
Files: models.NewRelatedFiles([]file.File{
&file.BaseFile{
Files: models.NewRelatedFiles([]models.File{
&models.BaseFile{
Path: path,
},
}),
@@ -69,8 +68,8 @@ func createFullGallery(id int) models.Gallery {
func createEmptyGallery(id int) models.Gallery {
return models.Gallery{
ID: id,
Files: models.NewRelatedFiles([]file.File{
&file.BaseFile{
Files: models.NewRelatedFiles([]models.File{
&models.BaseFile{
Path: path,
},
}),

View File

@@ -5,22 +5,25 @@ import (
"fmt"
"strings"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
)
type ImporterReaderWriter interface {
models.GalleryCreatorUpdater
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Gallery, error)
FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error)
}
type Importer struct {
ReaderWriter FullCreatorUpdater
StudioWriter studio.NameFinderCreator
PerformerWriter performer.NameFinderCreator
TagWriter tag.NameFinderCreator
FileFinder file.Getter
FolderFinder file.FolderGetter
ReaderWriter ImporterReaderWriter
StudioWriter models.StudioFinderCreator
PerformerWriter models.PerformerFinderCreator
TagWriter models.TagFinderCreator
FileFinder models.FileFinder
FolderFinder models.FolderFinder
Input jsonschema.Gallery
MissingRefBehaviour models.ImportMissingRefEnum
@@ -28,11 +31,6 @@ type Importer struct {
gallery models.Gallery
}
type FullCreatorUpdater interface {
FinderCreatorUpdater
Update(ctx context.Context, updatedGallery *models.Gallery) error
}
func (i *Importer) PreImport(ctx context.Context) error {
i.gallery = i.galleryJSONToGallery(i.Input)
@@ -251,7 +249,7 @@ func (i *Importer) createTags(ctx context.Context, names []string) ([]*models.Ta
}
func (i *Importer) populateFilesFolder(ctx context.Context) error {
files := make([]file.File, 0)
files := make([]models.File, 0)
for _, ref := range i.Input.ZipFiles {
path := ref
@@ -340,7 +338,7 @@ func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
var fileIDs []file.ID
var fileIDs []models.FileID
for _, f := range i.gallery.Files.List() {
fileIDs = append(fileIDs, f.Base().ID)
}

View File

@@ -6,7 +6,6 @@ import (
"testing"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@@ -68,7 +67,7 @@ func TestImporterPreImport(t *testing.T) {
Rating: &rating,
Organized: organized,
URL: url,
Files: models.NewRelatedFiles([]file.File{}),
Files: models.NewRelatedFiles([]models.File{}),
TagIDs: models.NewRelatedIDs([]int{}),
PerformerIDs: models.NewRelatedIDs([]int{}),
CreatedAt: createdAt,

View File

@@ -4,27 +4,10 @@ import (
"context"
"strconv"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
type Queryer interface {
Query(ctx context.Context, galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) ([]*models.Gallery, int, error)
}
type CountQueryer interface {
QueryCount(ctx context.Context, galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) (int, error)
}
type Finder interface {
FindByPath(ctx context.Context, p string) ([]*models.Gallery, error)
FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Gallery, error)
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Gallery, error)
FindByFingerprints(ctx context.Context, fp []file.Fingerprint) ([]*models.Gallery, error)
}
func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error) {
func CountByPerformerID(ctx context.Context, r models.GalleryQueryer, id int) (int, error) {
filter := &models.GalleryFilterType{
Performers: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -35,7 +18,7 @@ func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error
return r.QueryCount(ctx, filter, nil)
}
func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByStudioID(ctx context.Context, r models.GalleryQueryer, id int, depth *int) (int, error) {
filter := &models.GalleryFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -47,7 +30,7 @@ func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (i
return r.QueryCount(ctx, filter, nil)
}
func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByTagID(ctx context.Context, r models.GalleryQueryer, id int, depth *int) (int, error) {
filter := &models.GalleryFilterType{
Tags: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},

View File

@@ -7,39 +7,40 @@ import (
"strings"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
)
type FinderCreatorUpdater interface {
Finder
Create(ctx context.Context, newGallery *models.Gallery, fileIDs []file.ID) error
type ScanCreatorUpdater interface {
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error)
FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Gallery, error)
GetFiles(ctx context.Context, relatedID int) ([]models.File, error)
Create(ctx context.Context, newGallery *models.Gallery, fileIDs []models.FileID) error
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
AddFileID(ctx context.Context, id int, fileID file.ID) error
models.FileLoader
AddFileID(ctx context.Context, id int, fileID models.FileID) error
}
type SceneFinderUpdater interface {
type ScanSceneFinderUpdater interface {
FindByPath(ctx context.Context, p string) ([]*models.Scene, error)
Update(ctx context.Context, updatedScene *models.Scene) error
AddGalleryIDs(ctx context.Context, sceneID int, galleryIDs []int) error
}
type ImageFinderUpdater interface {
FindByZipFileID(ctx context.Context, zipFileID file.ID) ([]*models.Image, error)
type ScanImageFinderUpdater interface {
FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]*models.Image, error)
UpdatePartial(ctx context.Context, id int, partial models.ImagePartial) (*models.Image, error)
}
type ScanHandler struct {
CreatorUpdater FullCreatorUpdater
SceneFinderUpdater SceneFinderUpdater
ImageFinderUpdater ImageFinderUpdater
CreatorUpdater ScanCreatorUpdater
SceneFinderUpdater ScanSceneFinderUpdater
ImageFinderUpdater ScanImageFinderUpdater
PluginCache *plugin.Cache
}
func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File) error {
func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models.File) error {
baseFile := f.Base()
// try to match the file to a gallery
@@ -83,7 +84,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
logger.Infof("%s doesn't exist. Creating new gallery...", f.Base().Path)
if err := h.CreatorUpdater.Create(ctx, newGallery, []file.ID{baseFile.ID}); err != nil {
if err := h.CreatorUpdater.Create(ctx, newGallery, []models.FileID{baseFile.ID}); err != nil {
return fmt.Errorf("creating new gallery: %w", err)
}
@@ -112,7 +113,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
return nil
}
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Gallery, f file.File, updateExisting bool) error {
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Gallery, f models.File, updateExisting bool) error {
for _, i := range existing {
if err := i.LoadFiles(ctx, h.CreatorUpdater); err != nil {
return err
@@ -146,7 +147,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.
return nil
}
func (h *ScanHandler) associateScene(ctx context.Context, existing []*models.Gallery, f file.File) error {
func (h *ScanHandler) associateScene(ctx context.Context, existing []*models.Gallery, f models.File) error {
galleryIDs := make([]int, len(existing))
for i, g := range existing {
galleryIDs[i] = g.ID

View File

@@ -3,50 +3,25 @@ package gallery
import (
"context"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)
type FinderByFile interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Gallery, error)
}
type Repository interface {
models.GalleryFinder
FinderByFile
Destroy(ctx context.Context, id int) error
models.FileLoader
ImageUpdater
PartialUpdater
}
type PartialUpdater interface {
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
}
type ImageFinder interface {
FindByFolderID(ctx context.Context, folder file.FolderID) ([]*models.Image, error)
FindByZipFileID(ctx context.Context, zipFileID file.ID) ([]*models.Image, error)
FindByFolderID(ctx context.Context, folder models.FolderID) ([]*models.Image, error)
FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]*models.Image, error)
models.GalleryIDLoader
}
type ImageService interface {
Destroy(ctx context.Context, i *models.Image, fileDeleter *image.FileDeleter, deleteGenerated, deleteFile bool) error
DestroyZipImages(ctx context.Context, zipFile file.File, fileDeleter *image.FileDeleter, deleteGenerated bool) ([]*models.Image, error)
}
type ChapterRepository interface {
ChapterFinder
ChapterDestroyer
Update(ctx context.Context, updatedObject models.GalleryChapter) (*models.GalleryChapter, error)
DestroyZipImages(ctx context.Context, zipFile models.File, fileDeleter *image.FileDeleter, deleteGenerated bool) ([]*models.Image, error)
}
type Service struct {
Repository Repository
Repository models.GalleryReaderWriter
ImageFinder ImageFinder
ImageService ImageService
File file.Store
Folder file.FolderStore
File models.FileReaderWriter
Folder models.FolderReaderWriter
}

View File

@@ -54,7 +54,7 @@ func (s *Service) RemoveImages(ctx context.Context, g *models.Gallery, toRemove
return s.Updated(ctx, g.ID)
}
func AddPerformer(ctx context.Context, qb PartialUpdater, o *models.Gallery, performerID int) error {
func AddPerformer(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, performerID int) error {
_, err := qb.UpdatePartial(ctx, o.ID, models.GalleryPartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerID},
@@ -64,7 +64,7 @@ func AddPerformer(ctx context.Context, qb PartialUpdater, o *models.Gallery, per
return err
}
func AddTag(ctx context.Context, qb PartialUpdater, o *models.Gallery, tagID int) error {
func AddTag(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, tagID int) error {
_, err := qb.UpdatePartial(ctx, o.ID, models.GalleryPartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagID},

View File

@@ -13,8 +13,8 @@ import (
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/ffmpeg/transcoder"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
const (
@@ -23,7 +23,7 @@ const (
rows = 5
)
func Generate(encoder *ffmpeg.FFMpeg, videoFile *file.VideoFile) (*uint64, error) {
func Generate(encoder *ffmpeg.FFMpeg, videoFile *models.VideoFile) (*uint64, error) {
sprite, err := generateSprite(encoder, videoFile)
if err != nil {
return nil, err
@@ -76,7 +76,7 @@ func combineImages(images []image.Image) image.Image {
return montage
}
func generateSprite(encoder *ffmpeg.FFMpeg, videoFile *file.VideoFile) (image.Image, error) {
func generateSprite(encoder *ffmpeg.FFMpeg, videoFile *models.VideoFile) (image.Image, error) {
logger.Infof("[generator] generating phash sprite for %s", videoFile.Path)
// Generate sprite image offset by 5% on each end to avoid intro/outros

View File

@@ -10,10 +10,6 @@ import (
"github.com/stashapp/stash/pkg/models/paths"
)
type Destroyer interface {
Destroy(ctx context.Context, id int) error
}
// FileDeleter is an extension of file.Deleter that handles deletion of image files.
type FileDeleter struct {
*file.Deleter
@@ -45,7 +41,7 @@ func (s *Service) Destroy(ctx context.Context, i *models.Image, fileDeleter *Fil
// DestroyZipImages destroys all images in zip, optionally marking the files and generated files for deletion.
// Returns a slice of images that were destroyed.
func (s *Service) DestroyZipImages(ctx context.Context, zipFile file.File, fileDeleter *FileDeleter, deleteGenerated bool) ([]*models.Image, error) {
func (s *Service) DestroyZipImages(ctx context.Context, zipFile models.File, fileDeleter *FileDeleter, deleteGenerated bool) ([]*models.Image, error) {
var imgsDestroyed []*models.Image
imgs, err := s.Repository.FindByZipFileID(ctx, zipFile.Base().ID)

View File

@@ -6,7 +6,6 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
)
// ToBasicJSON converts a image object into its JSON object equivalent. It
@@ -53,7 +52,7 @@ func ToBasicJSON(image *models.Image) *jsonschema.Image {
// GetStudioName returns the name of the provided image's studio. It returns an
// empty string if there is no studio assigned to the image.
func GetStudioName(ctx context.Context, reader studio.Finder, image *models.Image) (string, error) {
func GetStudioName(ctx context.Context, reader models.StudioGetter, image *models.Image) (string, error) {
if image.StudioID != nil {
studio, err := reader.Find(ctx, *image.StudioID)
if err != nil {

View File

@@ -3,7 +3,6 @@ package image
import (
"errors"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@@ -45,8 +44,8 @@ var (
func createFullImage(id int) models.Image {
return models.Image{
ID: id,
Files: models.NewRelatedFiles([]file.File{
&file.BaseFile{
Files: models.NewRelatedFiles([]models.File{
&models.BaseFile{
Path: path,
},
}),

View File

@@ -5,13 +5,9 @@ import (
"fmt"
"strings"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
)
type GalleryFinder interface {
@@ -19,18 +15,18 @@ type GalleryFinder interface {
FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error)
}
type FullCreatorUpdater interface {
FinderCreatorUpdater
Update(ctx context.Context, updatedImage *models.Image) error
type ImporterReaderWriter interface {
models.ImageCreatorUpdater
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Image, error)
}
type Importer struct {
ReaderWriter FullCreatorUpdater
FileFinder file.Getter
StudioWriter studio.NameFinderCreator
ReaderWriter ImporterReaderWriter
FileFinder models.FileFinder
StudioWriter models.StudioFinderCreator
GalleryFinder GalleryFinder
PerformerWriter performer.NameFinderCreator
TagWriter tag.NameFinderCreator
PerformerWriter models.PerformerFinderCreator
TagWriter models.TagFinderCreator
Input jsonschema.Image
MissingRefBehaviour models.ImportMissingRefEnum
@@ -99,7 +95,7 @@ func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image {
}
func (i *Importer) populateFiles(ctx context.Context) error {
files := make([]file.File, 0)
files := make([]models.File, 0)
for _, ref := range i.Input.Files {
path := ref
@@ -330,7 +326,7 @@ func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
var fileIDs []file.ID
var fileIDs []models.FileID
for _, f := range i.image.Files.List() {
fileIDs = append(fileIDs, f.Base().ID)
}
@@ -360,7 +356,7 @@ func (i *Importer) Update(ctx context.Context, id int) error {
return nil
}
func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
tags, err := tagWriter.FindByNames(ctx, names, false)
if err != nil {
return nil, err
@@ -395,7 +391,7 @@ func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []st
return tags, nil
}
func createTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string) ([]*models.Tag, error) {
func createTags(ctx context.Context, tagWriter models.TagCreator, names []string) ([]*models.Tag, error) {
var ret []*models.Tag
for _, name := range names {
newTag := models.NewTag(name)

View File

@@ -7,14 +7,6 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type Queryer interface {
Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error)
}
type CountQueryer interface {
QueryCount(ctx context.Context, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) (int, error)
}
// QueryOptions returns a ImageQueryResult populated with the provided filters.
func QueryOptions(imageFilter *models.ImageFilterType, findFilter *models.FindFilterType, count bool) models.ImageQueryOptions {
return models.ImageQueryOptions{
@@ -27,7 +19,7 @@ func QueryOptions(imageFilter *models.ImageFilterType, findFilter *models.FindFi
}
// Query queries for images using the provided filters.
func Query(ctx context.Context, qb Queryer, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) ([]*models.Image, error) {
func Query(ctx context.Context, qb models.ImageQueryer, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) ([]*models.Image, error) {
result, err := qb.Query(ctx, QueryOptions(imageFilter, findFilter, false))
if err != nil {
return nil, err
@@ -41,7 +33,7 @@ func Query(ctx context.Context, qb Queryer, imageFilter *models.ImageFilterType,
return images, nil
}
func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error) {
func CountByPerformerID(ctx context.Context, r models.ImageQueryer, id int) (int, error) {
filter := &models.ImageFilterType{
Performers: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -52,7 +44,7 @@ func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error
return r.QueryCount(ctx, filter, nil)
}
func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByStudioID(ctx context.Context, r models.ImageQueryer, id int, depth *int) (int, error) {
filter := &models.ImageFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -64,7 +56,7 @@ func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (i
return r.QueryCount(ctx, filter, nil)
}
func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByTagID(ctx context.Context, r models.ImageQueryer, id int, depth *int) (int, error) {
filter := &models.ImageFilterType{
Tags: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -76,7 +68,7 @@ func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int,
return r.QueryCount(ctx, filter, nil)
}
func FindByGalleryID(ctx context.Context, r Queryer, galleryID int, sortBy string, sortDir models.SortDirectionEnum) ([]*models.Image, error) {
func FindByGalleryID(ctx context.Context, r models.ImageQueryer, galleryID int, sortBy string, sortDir models.SortDirectionEnum) ([]*models.Image, error) {
perPage := -1
findFilter := models.FindFilterType{
@@ -99,7 +91,7 @@ func FindByGalleryID(ctx context.Context, r Queryer, galleryID int, sortBy strin
}, &findFilter)
}
func FindGalleryCover(ctx context.Context, r Queryer, galleryID int, galleryCoverRegex string) (*models.Image, error) {
func FindGalleryCover(ctx context.Context, r models.ImageQueryer, galleryID int, galleryCoverRegex string) (*models.Image, error) {
const useCoverJpg = true
img, err := findGalleryCover(ctx, r, galleryID, useCoverJpg, galleryCoverRegex)
if err != nil {
@@ -114,7 +106,7 @@ func FindGalleryCover(ctx context.Context, r Queryer, galleryID int, galleryCove
return findGalleryCover(ctx, r, galleryID, !useCoverJpg, galleryCoverRegex)
}
func findGalleryCover(ctx context.Context, r Queryer, galleryID int, useCoverJpg bool, galleryCoverRegex string) (*models.Image, error) {
func findGalleryCover(ctx context.Context, r models.ImageQueryer, galleryID int, useCoverJpg bool, galleryCoverRegex string) (*models.Image, error) {
// try to find cover.jpg in the gallery
perPage := 1
sortBy := "path"

View File

@@ -8,7 +8,6 @@ import (
"path/filepath"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/paths"
@@ -21,21 +20,22 @@ var (
ErrNotImageFile = errors.New("not an image file")
)
type FinderCreatorUpdater interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Image, error)
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Image, error)
FindByFingerprints(ctx context.Context, fp []file.Fingerprint) ([]*models.Image, error)
type ScanCreatorUpdater interface {
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Image, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Image, error)
FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Image, error)
GetFiles(ctx context.Context, relatedID int) ([]models.File, error)
GetGalleryIDs(ctx context.Context, relatedID int) ([]int, error)
Create(ctx context.Context, newImage *models.ImageCreateInput) error
UpdatePartial(ctx context.Context, id int, updatedImage models.ImagePartial) (*models.Image, error)
AddFileID(ctx context.Context, id int, fileID file.ID) error
models.GalleryIDLoader
models.FileLoader
AddFileID(ctx context.Context, id int, fileID models.FileID) error
}
type GalleryFinderCreator interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Gallery, error)
Create(ctx context.Context, newObject *models.Gallery, fileIDs []file.ID) error
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Gallery, error)
Create(ctx context.Context, newObject *models.Gallery, fileIDs []models.FileID) error
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
}
@@ -44,11 +44,11 @@ type ScanConfig interface {
}
type ScanGenerator interface {
Generate(ctx context.Context, i *models.Image, f file.File) error
Generate(ctx context.Context, i *models.Image, f models.File) error
}
type ScanHandler struct {
CreatorUpdater FinderCreatorUpdater
CreatorUpdater ScanCreatorUpdater
GalleryFinder GalleryFinderCreator
ScanGenerator ScanGenerator
@@ -80,7 +80,7 @@ func (h *ScanHandler) validate() error {
return nil
}
func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File) error {
func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models.File) error {
if err := h.validate(); err != nil {
return err
}
@@ -130,7 +130,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
if err := h.CreatorUpdater.Create(ctx, &models.ImageCreateInput{
Image: newImage,
FileIDs: []file.ID{imageFile.ID},
FileIDs: []models.FileID{imageFile.ID},
}); err != nil {
return fmt.Errorf("creating new image: %w", err)
}
@@ -151,8 +151,8 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
// remove the old thumbnail if the checksum changed - we'll regenerate it
if oldFile != nil {
oldHash := oldFile.Base().Fingerprints.GetString(file.FingerprintTypeMD5)
newHash := f.Base().Fingerprints.GetString(file.FingerprintTypeMD5)
oldHash := oldFile.Base().Fingerprints.GetString(models.FingerprintTypeMD5)
newHash := f.Base().Fingerprints.GetString(models.FingerprintTypeMD5)
if oldHash != "" && newHash != "" && oldHash != newHash {
// remove cache dir of gallery
@@ -173,7 +173,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
return nil
}
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Image, f *file.BaseFile, updateExisting bool) error {
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Image, f *models.BaseFile, updateExisting bool) error {
for _, i := range existing {
if err := i.LoadFiles(ctx, h.CreatorUpdater); err != nil {
return err
@@ -239,7 +239,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.
return nil
}
func (h *ScanHandler) getOrCreateFolderBasedGallery(ctx context.Context, f file.File) (*models.Gallery, error) {
func (h *ScanHandler) getOrCreateFolderBasedGallery(ctx context.Context, f models.File) (*models.Gallery, error) {
folderID := f.Base().ParentFolderID
g, err := h.GalleryFinder.FindByFolderID(ctx, folderID)
if err != nil {
@@ -299,7 +299,7 @@ func (h *ScanHandler) associateFolderImages(ctx context.Context, g *models.Galle
return nil
}
func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile file.File) (*models.Gallery, error) {
func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile models.File) (*models.Gallery, error) {
g, err := h.GalleryFinder.FindByFileID(ctx, zipFile.Base().ID)
if err != nil {
return nil, fmt.Errorf("finding zip based gallery: %w", err)
@@ -319,7 +319,7 @@ func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile fi
logger.Infof("%s doesn't exist. Creating new gallery...", zipFile.Base().Path)
if err := h.GalleryFinder.Create(ctx, newGallery, []file.ID{zipFile.Base().ID}); err != nil {
if err := h.GalleryFinder.Create(ctx, newGallery, []models.FileID{zipFile.Base().ID}); err != nil {
return nil, fmt.Errorf("creating zip-based gallery: %w", err)
}
@@ -328,7 +328,7 @@ func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile fi
return newGallery, nil
}
func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f file.File) (*models.Gallery, error) {
func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f models.File) (*models.Gallery, error) {
// don't create folder-based galleries for files in zip file
if f.Base().ZipFile != nil {
return h.getOrCreateZipBasedGallery(ctx, f.Base().ZipFile)
@@ -357,7 +357,7 @@ func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f file.File) (*mod
return nil, nil
}
func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *models.Image, f file.File) (*models.Gallery, error) {
func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *models.Image, f models.File) (*models.Gallery, error) {
g, err := h.getOrCreateGallery(ctx, f)
if err != nil {
return nil, err

View File

@@ -1,24 +1,10 @@
package image
import (
"context"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
type FinderByFile interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Image, error)
FindByZipFileID(ctx context.Context, zipFileID file.ID) ([]*models.Image, error)
}
type Repository interface {
FinderByFile
Destroyer
models.FileLoader
}
type Service struct {
File file.Store
Repository Repository
File models.FileReaderWriter
Repository models.ImageReaderWriter
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/stashapp/stash/pkg/ffmpeg/transcoder"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
)
const ffmpegImageQuality = 5
@@ -68,7 +69,7 @@ func NewThumbnailEncoder(ffmpegEncoder *ffmpeg.FFMpeg, ffProbe ffmpeg.FFProbe, c
// the provided max size. It resizes based on the largest X/Y direction.
// It returns nil and an error if an error occurs reading, decoding or encoding
// the image, or if the image is not suitable for thumbnails.
func (e *ThumbnailEncoder) GetThumbnail(f file.File, maxSize int) ([]byte, error) {
func (e *ThumbnailEncoder) GetThumbnail(f models.File, maxSize int) ([]byte, error) {
reader, err := f.Open(&file.OsFS{})
if err != nil {
return nil, err
@@ -82,7 +83,7 @@ func (e *ThumbnailEncoder) GetThumbnail(f file.File, maxSize int) ([]byte, error
data := buf.Bytes()
if imageFile, ok := f.(*file.ImageFile); ok {
if imageFile, ok := f.(*models.ImageFile); ok {
format := imageFile.Format
animated := imageFile.Format == formatGif
@@ -98,7 +99,7 @@ func (e *ThumbnailEncoder) GetThumbnail(f file.File, maxSize int) ([]byte, error
}
// Videofiles can only be thumbnailed with ffmpeg
if _, ok := f.(*file.VideoFile); ok {
if _, ok := f.(*models.VideoFile); ok {
return e.ffmpegImageThumbnail(buf, maxSize)
}

View File

@@ -6,11 +6,7 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type PartialUpdater interface {
UpdatePartial(ctx context.Context, id int, partial models.ImagePartial) (*models.Image, error)
}
func AddPerformer(ctx context.Context, qb PartialUpdater, i *models.Image, performerID int) error {
func AddPerformer(ctx context.Context, qb models.ImageUpdater, i *models.Image, performerID int) error {
_, err := qb.UpdatePartial(ctx, i.ID, models.ImagePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerID},
@@ -21,7 +17,7 @@ func AddPerformer(ctx context.Context, qb PartialUpdater, i *models.Image, perfo
return err
}
func AddTag(ctx context.Context, qb PartialUpdater, i *models.Image, tagID int) error {
func AddTag(ctx context.Context, qb models.ImageUpdater, i *models.Image, tagID int) error {
_, err := qb.UpdatePartial(ctx, i.ID, models.ImagePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagID},

View File

@@ -20,7 +20,7 @@ type Cache struct {
// against. This means that performers with single-letter words in their names could potentially
// be missed.
// This query is expensive, so it's queried once and cached, if the cache if provided.
func getSingleLetterPerformers(ctx context.Context, c *Cache, reader PerformerAutoTagQueryer) ([]*models.Performer, error) {
func getSingleLetterPerformers(ctx context.Context, c *Cache, reader models.PerformerAutoTagQueryer) ([]*models.Performer, error) {
if c == nil {
c = &Cache{}
}
@@ -53,7 +53,7 @@ func getSingleLetterPerformers(ctx context.Context, c *Cache, reader PerformerAu
// getSingleLetterStudios returns all studios with names that start with single character words.
// See getSingleLetterPerformers for details.
func getSingleLetterStudios(ctx context.Context, c *Cache, reader StudioAutoTagQueryer) ([]*models.Studio, error) {
func getSingleLetterStudios(ctx context.Context, c *Cache, reader models.StudioAutoTagQueryer) ([]*models.Studio, error) {
if c == nil {
c = &Cache{}
}
@@ -86,7 +86,7 @@ func getSingleLetterStudios(ctx context.Context, c *Cache, reader StudioAutoTagQ
// getSingleLetterTags returns all tags with names that start with single character words.
// See getSingleLetterPerformers for details.
func getSingleLetterTags(ctx context.Context, c *Cache, reader TagAutoTagQueryer) ([]*models.Tag, error) {
func getSingleLetterTags(ctx context.Context, c *Cache, reader models.TagAutoTagQueryer) ([]*models.Tag, error) {
if c == nil {
c = &Cache{}
}

View File

@@ -14,8 +14,6 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
)
const (
@@ -28,24 +26,6 @@ const (
var separatorRE = regexp.MustCompile(separatorPattern)
type PerformerAutoTagQueryer interface {
Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error)
QueryForAutoTag(ctx context.Context, words []string) ([]*models.Performer, error)
models.AliasLoader
}
type StudioAutoTagQueryer interface {
QueryForAutoTag(ctx context.Context, words []string) ([]*models.Studio, error)
studio.Queryer
GetAliases(ctx context.Context, studioID int) ([]string, error)
}
type TagAutoTagQueryer interface {
QueryForAutoTag(ctx context.Context, words []string) ([]*models.Tag, error)
tag.Queryer
GetAliases(ctx context.Context, tagID int) ([]string, error)
}
func getPathQueryRegex(name string) string {
// escape specific regex characters
name = regexp.QuoteMeta(name)
@@ -146,7 +126,7 @@ func regexpMatchesPath(r *regexp.Regexp, path string) int {
return found[len(found)-1][0]
}
func getPerformers(ctx context.Context, words []string, performerReader PerformerAutoTagQueryer, cache *Cache) ([]*models.Performer, error) {
func getPerformers(ctx context.Context, words []string, performerReader models.PerformerAutoTagQueryer, cache *Cache) ([]*models.Performer, error) {
performers, err := performerReader.QueryForAutoTag(ctx, words)
if err != nil {
return nil, err
@@ -160,7 +140,7 @@ func getPerformers(ctx context.Context, words []string, performerReader Performe
return append(performers, swPerformers...), nil
}
func PathToPerformers(ctx context.Context, path string, reader PerformerAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Performer, error) {
func PathToPerformers(ctx context.Context, path string, reader models.PerformerAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Performer, error) {
words := getPathWords(path, trimExt)
performers, err := getPerformers(ctx, words, reader, cache)
@@ -198,7 +178,7 @@ func PathToPerformers(ctx context.Context, path string, reader PerformerAutoTagQ
return ret, nil
}
func getStudios(ctx context.Context, words []string, reader StudioAutoTagQueryer, cache *Cache) ([]*models.Studio, error) {
func getStudios(ctx context.Context, words []string, reader models.StudioAutoTagQueryer, cache *Cache) ([]*models.Studio, error) {
studios, err := reader.QueryForAutoTag(ctx, words)
if err != nil {
return nil, err
@@ -215,7 +195,7 @@ func getStudios(ctx context.Context, words []string, reader StudioAutoTagQueryer
// PathToStudio returns the Studio that matches the given path.
// Where multiple matching studios are found, the one that matches the latest
// position in the path is returned.
func PathToStudio(ctx context.Context, path string, reader StudioAutoTagQueryer, cache *Cache, trimExt bool) (*models.Studio, error) {
func PathToStudio(ctx context.Context, path string, reader models.StudioAutoTagQueryer, cache *Cache, trimExt bool) (*models.Studio, error) {
words := getPathWords(path, trimExt)
candidates, err := getStudios(ctx, words, reader, cache)
@@ -249,7 +229,7 @@ func PathToStudio(ctx context.Context, path string, reader StudioAutoTagQueryer,
return ret, nil
}
func getTags(ctx context.Context, words []string, reader TagAutoTagQueryer, cache *Cache) ([]*models.Tag, error) {
func getTags(ctx context.Context, words []string, reader models.TagAutoTagQueryer, cache *Cache) ([]*models.Tag, error) {
tags, err := reader.QueryForAutoTag(ctx, words)
if err != nil {
return nil, err
@@ -263,7 +243,7 @@ func getTags(ctx context.Context, words []string, reader TagAutoTagQueryer, cach
return append(tags, swTags...), nil
}
func PathToTags(ctx context.Context, path string, reader TagAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Tag, error) {
func PathToTags(ctx context.Context, path string, reader models.TagAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Tag, error) {
words := getPathWords(path, trimExt)
tags, err := getTags(ctx, words, reader, cache)
@@ -299,7 +279,7 @@ func PathToTags(ctx context.Context, path string, reader TagAutoTagQueryer, cach
return ret, nil
}
func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReader scene.Queryer, fn func(ctx context.Context, scene *models.Scene) error) error {
func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReader models.SceneQueryer, fn func(ctx context.Context, scene *models.Scene) error) error {
regex := getPathQueryRegex(name)
organized := false
filter := models.SceneFilterType{
@@ -358,7 +338,7 @@ func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReade
return nil
}
func PathToImagesFn(ctx context.Context, name string, paths []string, imageReader image.Queryer, fn func(ctx context.Context, scene *models.Image) error) error {
func PathToImagesFn(ctx context.Context, name string, paths []string, imageReader models.ImageQueryer, fn func(ctx context.Context, scene *models.Image) error) error {
regex := getPathQueryRegex(name)
organized := false
filter := models.ImageFilterType{
@@ -417,7 +397,7 @@ func PathToImagesFn(ctx context.Context, name string, paths []string, imageReade
return nil
}
func PathToGalleriesFn(ctx context.Context, name string, paths []string, galleryReader gallery.Queryer, fn func(ctx context.Context, scene *models.Gallery) error) error {
func PathToGalleriesFn(ctx context.Context, name string, paths []string, galleryReader models.GalleryQueryer, fn func(ctx context.Context, scene *models.Gallery) error) error {
regex := getPathQueryRegex(name)
organized := false
filter := models.GalleryFilterType{

View File

@@ -58,7 +58,7 @@ func ScrapedPerformer(ctx context.Context, qb PerformerFinder, p *models.Scraped
}
type StudioFinder interface {
studio.Queryer
models.StudioQueryer
FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Studio, error)
}
@@ -134,7 +134,7 @@ func ScrapedMovie(ctx context.Context, qb MovieNamesFinder, m *models.ScrapedMov
// ScrapedTag matches the provided tag with the tags
// in the database and sets the ID field if one is found.
func ScrapedTag(ctx context.Context, qb tag.Queryer, s *models.ScrapedTag) error {
func ScrapedTag(ctx context.Context, qb models.TagQueryer, s *models.ScrapedTag) error {
if s.StoredID != nil {
return nil
}

View File

@@ -4,8 +4,6 @@ import (
"context"
"path/filepath"
"strings"
"github.com/stashapp/stash/pkg/file"
)
type FileQueryOptions struct {
@@ -57,24 +55,24 @@ func PathsFileFilter(paths []string) *FileFilterType {
type FileQueryResult struct {
// can't use QueryResult because id type is wrong
IDs []file.ID
IDs []FileID
Count int
finder file.Finder
files []file.File
getter FileGetter
files []File
resolveErr error
}
func NewFileQueryResult(finder file.Finder) *FileQueryResult {
func NewFileQueryResult(fileGetter FileGetter) *FileQueryResult {
return &FileQueryResult{
finder: finder,
getter: fileGetter,
}
}
func (r *FileQueryResult) Resolve(ctx context.Context) ([]file.File, error) {
func (r *FileQueryResult) Resolve(ctx context.Context) ([]File, error) {
// cache results
if r.files == nil && r.resolveErr == nil {
r.files, r.resolveErr = r.finder.Find(ctx, r.IDs...)
r.files, r.resolveErr = r.getter.Find(ctx, r.IDs...)
}
return r.files, r.resolveErr
}

View File

@@ -1,4 +1,9 @@
package file
package models
import (
"fmt"
"strconv"
)
var (
FingerprintTypeOshash = "oshash"
@@ -12,6 +17,15 @@ type Fingerprint struct {
Fingerprint interface{}
}
func (f *Fingerprint) Value() string {
switch v := f.Fingerprint.(type) {
case int64:
return strconv.FormatUint(uint64(v), 16)
default:
return fmt.Sprintf("%v", f.Fingerprint)
}
}
type Fingerprints []Fingerprint
func (f *Fingerprints) Remove(type_ string) {
@@ -114,8 +128,3 @@ func (f Fingerprints) AppendUnique(o Fingerprint) Fingerprints {
return append(f, o)
}
// FingerprintCalculator calculates a fingerprint for the provided file.
type FingerprintCalculator interface {
CalculateFingerprints(f *BaseFile, o Opener, useExisting bool) ([]Fingerprint, error)
}

View File

@@ -1,4 +1,4 @@
package file
package models
import "testing"

27
pkg/models/fs.go Normal file
View File

@@ -0,0 +1,27 @@
package models
import (
"io"
"io/fs"
)
// FileOpener provides an interface to open a file.
type FileOpener interface {
Open() (io.ReadCloser, error)
}
// FS represents a file system.
type FS interface {
Stat(name string) (fs.FileInfo, error)
Lstat(name string) (fs.FileInfo, error)
Open(name string) (fs.ReadDirFile, error)
OpenZip(name string) (ZipFS, error)
IsPathCaseSensitive(path string) (bool, error)
}
// ZipFS represents a zip file system.
type ZipFS interface {
FS
io.Closer
OpenOnly(name string) (io.ReadCloser, error)
}

View File

@@ -1,11 +1,5 @@
package models
import (
"context"
"github.com/stashapp/stash/pkg/file"
)
type GalleryFilterType struct {
And *GalleryFilterType `json:"AND"`
Or *GalleryFilterType `json:"OR"`
@@ -86,40 +80,3 @@ type GalleryDestroyInput struct {
DeleteFile *bool `json:"delete_file"`
DeleteGenerated *bool `json:"delete_generated"`
}
type GalleryFinder interface {
FindMany(ctx context.Context, ids []int) ([]*Gallery, error)
}
type GalleryReader interface {
Find(ctx context.Context, id int) (*Gallery, error)
GalleryFinder
FindByChecksum(ctx context.Context, checksum string) ([]*Gallery, error)
FindByChecksums(ctx context.Context, checksums []string) ([]*Gallery, error)
FindByPath(ctx context.Context, path string) ([]*Gallery, error)
FindBySceneID(ctx context.Context, sceneID int) ([]*Gallery, error)
FindByImageID(ctx context.Context, imageID int) ([]*Gallery, error)
SceneIDLoader
PerformerIDLoader
TagIDLoader
Count(ctx context.Context) (int, error)
All(ctx context.Context) ([]*Gallery, error)
Query(ctx context.Context, galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int, error)
QueryCount(ctx context.Context, galleryFilter *GalleryFilterType, findFilter *FindFilterType) (int, error)
GetImageIDs(ctx context.Context, galleryID int) ([]int, error)
}
type GalleryWriter interface {
Create(ctx context.Context, newGallery *Gallery, fileIDs []file.ID) error
Update(ctx context.Context, updatedGallery *Gallery) error
UpdatePartial(ctx context.Context, id int, updatedGallery GalleryPartial) (*Gallery, error)
Destroy(ctx context.Context, id int) error
UpdateImages(ctx context.Context, galleryID int, imageIDs []int) error
}
type GalleryReaderWriter interface {
GalleryReader
GalleryWriter
}

View File

@@ -1,21 +0,0 @@
package models
import "context"
type GalleryChapterReader interface {
Find(ctx context.Context, id int) (*GalleryChapter, error)
FindMany(ctx context.Context, ids []int) ([]*GalleryChapter, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*GalleryChapter, error)
}
type GalleryChapterWriter interface {
Create(ctx context.Context, newGalleryChapter *GalleryChapter) error
Update(ctx context.Context, updatedGalleryChapter *GalleryChapter) error
UpdatePartial(ctx context.Context, id int, updatedGalleryChapter GalleryChapterPartial) (*GalleryChapter, error)
Destroy(ctx context.Context, id int) error
}
type GalleryChapterReaderWriter interface {
GalleryChapterReader
GalleryChapterWriter
}

View File

@@ -77,60 +77,21 @@ type ImageQueryResult struct {
Megapixels float64
TotalSize float64
finder ImageFinder
getter ImageGetter
images []*Image
resolveErr error
}
func NewImageQueryResult(finder ImageFinder) *ImageQueryResult {
func NewImageQueryResult(getter ImageGetter) *ImageQueryResult {
return &ImageQueryResult{
finder: finder,
getter: getter,
}
}
func (r *ImageQueryResult) Resolve(ctx context.Context) ([]*Image, error) {
// cache results
if r.images == nil && r.resolveErr == nil {
r.images, r.resolveErr = r.finder.FindMany(ctx, r.IDs)
r.images, r.resolveErr = r.getter.FindMany(ctx, r.IDs)
}
return r.images, r.resolveErr
}
type ImageFinder interface {
// TODO - rename to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Image, error)
}
type ImageReader interface {
ImageFinder
// TODO - remove this in another PR
Find(ctx context.Context, id int) (*Image, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Image, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Image, error)
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
Count(ctx context.Context) (int, error)
Size(ctx context.Context) (float64, error)
All(ctx context.Context) ([]*Image, error)
Query(ctx context.Context, options ImageQueryOptions) (*ImageQueryResult, error)
QueryCount(ctx context.Context, imageFilter *ImageFilterType, findFilter *FindFilterType) (int, error)
GalleryIDLoader
PerformerIDLoader
TagIDLoader
}
type ImageWriter interface {
Create(ctx context.Context, newImage *ImageCreateInput) error
Update(ctx context.Context, updatedImage *Image) error
UpdatePartial(ctx context.Context, id int, partial ImagePartial) (*Image, error)
IncrementOCounter(ctx context.Context, id int) (int, error)
DecrementOCounter(ctx context.Context, id int) (int, error)
ResetOCounter(ctx context.Context, id int) (int, error)
Destroy(ctx context.Context, id int) error
}
type ImageReaderWriter interface {
ImageReader
ImageWriter
}

View File

@@ -0,0 +1,350 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package mocks
import (
context "context"
fs "io/fs"
mock "github.com/stretchr/testify/mock"
models "github.com/stashapp/stash/pkg/models"
)
// FileReaderWriter is an autogenerated mock type for the FileReaderWriter type
type FileReaderWriter struct {
mock.Mock
}
// CountAllInPaths provides a mock function with given fields: ctx, p
func (_m *FileReaderWriter) CountAllInPaths(ctx context.Context, p []string) (int, error) {
ret := _m.Called(ctx, p)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, []string) int); ok {
r0 = rf(ctx, p)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok {
r1 = rf(ctx, p)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CountByFolderID provides a mock function with given fields: ctx, folderID
func (_m *FileReaderWriter) CountByFolderID(ctx context.Context, folderID models.FolderID) (int, error) {
ret := _m.Called(ctx, folderID)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, models.FolderID) int); ok {
r0 = rf(ctx, folderID)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FolderID) error); ok {
r1 = rf(ctx, folderID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Create provides a mock function with given fields: ctx, f
func (_m *FileReaderWriter) Create(ctx context.Context, f models.File) error {
ret := _m.Called(ctx, f)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.File) error); ok {
r0 = rf(ctx, f)
} else {
r0 = ret.Error(0)
}
return r0
}
// Destroy provides a mock function with given fields: ctx, id
func (_m *FileReaderWriter) Destroy(ctx context.Context, id models.FileID) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Find provides a mock function with given fields: ctx, id
func (_m *FileReaderWriter) Find(ctx context.Context, id ...models.FileID) ([]models.File, error) {
_va := make([]interface{}, len(id))
for _i := range id {
_va[_i] = id[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, ...models.FileID) []models.File); ok {
r0 = rf(ctx, id...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, ...models.FileID) error); ok {
r1 = rf(ctx, id...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindAllByPath provides a mock function with given fields: ctx, path
func (_m *FileReaderWriter) FindAllByPath(ctx context.Context, path string) ([]models.File, error) {
ret := _m.Called(ctx, path)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, string) []models.File); ok {
r0 = rf(ctx, path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, path)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindAllInPaths provides a mock function with given fields: ctx, p, limit, offset
func (_m *FileReaderWriter) FindAllInPaths(ctx context.Context, p []string, limit int, offset int) ([]models.File, error) {
ret := _m.Called(ctx, p, limit, offset)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, []string, int, int) []models.File); ok {
r0 = rf(ctx, p, limit, offset)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []string, int, int) error); ok {
r1 = rf(ctx, p, limit, offset)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFileInfo provides a mock function with given fields: ctx, info, size
func (_m *FileReaderWriter) FindByFileInfo(ctx context.Context, info fs.FileInfo, size int64) ([]models.File, error) {
ret := _m.Called(ctx, info, size)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, fs.FileInfo, int64) []models.File); ok {
r0 = rf(ctx, info, size)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, fs.FileInfo, int64) error); ok {
r1 = rf(ctx, info, size)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFingerprint provides a mock function with given fields: ctx, fp
func (_m *FileReaderWriter) FindByFingerprint(ctx context.Context, fp models.Fingerprint) ([]models.File, error) {
ret := _m.Called(ctx, fp)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, models.Fingerprint) []models.File); ok {
r0 = rf(ctx, fp)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Fingerprint) error); ok {
r1 = rf(ctx, fp)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByPath provides a mock function with given fields: ctx, path
func (_m *FileReaderWriter) FindByPath(ctx context.Context, path string) (models.File, error) {
ret := _m.Called(ctx, path)
var r0 models.File
if rf, ok := ret.Get(0).(func(context.Context, string) models.File); ok {
r0 = rf(ctx, path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, path)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByZipFileID provides a mock function with given fields: ctx, zipFileID
func (_m *FileReaderWriter) FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]models.File, error) {
ret := _m.Called(ctx, zipFileID)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []models.File); ok {
r0 = rf(ctx, zipFileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, zipFileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetCaptions provides a mock function with given fields: ctx, fileID
func (_m *FileReaderWriter) GetCaptions(ctx context.Context, fileID models.FileID) ([]*models.VideoCaption, error) {
ret := _m.Called(ctx, fileID)
var r0 []*models.VideoCaption
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.VideoCaption); ok {
r0 = rf(ctx, fileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.VideoCaption)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IsPrimary provides a mock function with given fields: ctx, fileID
func (_m *FileReaderWriter) IsPrimary(ctx context.Context, fileID models.FileID) (bool, error) {
ret := _m.Called(ctx, fileID)
var r0 bool
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) bool); ok {
r0 = rf(ctx, fileID)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Query provides a mock function with given fields: ctx, options
func (_m *FileReaderWriter) Query(ctx context.Context, options models.FileQueryOptions) (*models.FileQueryResult, error) {
ret := _m.Called(ctx, options)
var r0 *models.FileQueryResult
if rf, ok := ret.Get(0).(func(context.Context, models.FileQueryOptions) *models.FileQueryResult); ok {
r0 = rf(ctx, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.FileQueryResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileQueryOptions) error); ok {
r1 = rf(ctx, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, f
func (_m *FileReaderWriter) Update(ctx context.Context, f models.File) error {
ret := _m.Called(ctx, f)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.File) error); ok {
r0 = rf(ctx, f)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateCaptions provides a mock function with given fields: ctx, fileID, captions
func (_m *FileReaderWriter) UpdateCaptions(ctx context.Context, fileID models.FileID, captions []*models.VideoCaption) error {
ret := _m.Called(ctx, fileID, captions)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.FileID, []*models.VideoCaption) error); ok {
r0 = rf(ctx, fileID, captions)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -0,0 +1,193 @@
// Code generated by mockery v2.10.0. DO NOT EDIT.
package mocks
import (
context "context"
models "github.com/stashapp/stash/pkg/models"
mock "github.com/stretchr/testify/mock"
)
// FolderReaderWriter is an autogenerated mock type for the FolderReaderWriter type
type FolderReaderWriter struct {
mock.Mock
}
// CountAllInPaths provides a mock function with given fields: ctx, p
func (_m *FolderReaderWriter) CountAllInPaths(ctx context.Context, p []string) (int, error) {
ret := _m.Called(ctx, p)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, []string) int); ok {
r0 = rf(ctx, p)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok {
r1 = rf(ctx, p)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Create provides a mock function with given fields: ctx, f
func (_m *FolderReaderWriter) Create(ctx context.Context, f *models.Folder) error {
ret := _m.Called(ctx, f)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Folder) error); ok {
r0 = rf(ctx, f)
} else {
r0 = ret.Error(0)
}
return r0
}
// Destroy provides a mock function with given fields: ctx, id
func (_m *FolderReaderWriter) Destroy(ctx context.Context, id models.FolderID) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.FolderID) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// Find provides a mock function with given fields: ctx, id
func (_m *FolderReaderWriter) Find(ctx context.Context, id models.FolderID) (*models.Folder, error) {
ret := _m.Called(ctx, id)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, models.FolderID) *models.Folder); ok {
r0 = rf(ctx, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FolderID) error); ok {
r1 = rf(ctx, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindAllInPaths provides a mock function with given fields: ctx, p, limit, offset
func (_m *FolderReaderWriter) FindAllInPaths(ctx context.Context, p []string, limit int, offset int) ([]*models.Folder, error) {
ret := _m.Called(ctx, p, limit, offset)
var r0 []*models.Folder
if rf, ok := ret.Get(0).(func(context.Context, []string, int, int) []*models.Folder); ok {
r0 = rf(ctx, p, limit, offset)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []string, int, int) error); ok {
r1 = rf(ctx, p, limit, offset)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByParentFolderID provides a mock function with given fields: ctx, parentFolderID
func (_m *FolderReaderWriter) FindByParentFolderID(ctx context.Context, parentFolderID models.FolderID) ([]*models.Folder, error) {
ret := _m.Called(ctx, parentFolderID)
var r0 []*models.Folder
if rf, ok := ret.Get(0).(func(context.Context, models.FolderID) []*models.Folder); ok {
r0 = rf(ctx, parentFolderID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FolderID) error); ok {
r1 = rf(ctx, parentFolderID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByPath provides a mock function with given fields: ctx, path
func (_m *FolderReaderWriter) FindByPath(ctx context.Context, path string) (*models.Folder, error) {
ret := _m.Called(ctx, path)
var r0 *models.Folder
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Folder); ok {
r0 = rf(ctx, path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, path)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByZipFileID provides a mock function with given fields: ctx, zipFileID
func (_m *FolderReaderWriter) FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]*models.Folder, error) {
ret := _m.Called(ctx, zipFileID)
var r0 []*models.Folder
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.Folder); ok {
r0 = rf(ctx, zipFileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Folder)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, zipFileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, f
func (_m *FolderReaderWriter) Update(ctx context.Context, f *models.Folder) error {
ret := _m.Called(ctx, f)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Folder) error); ok {
r0 = rf(ctx, f)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -5,10 +5,8 @@ package mocks
import (
context "context"
file "github.com/stashapp/stash/pkg/file"
mock "github.com/stretchr/testify/mock"
models "github.com/stashapp/stash/pkg/models"
mock "github.com/stretchr/testify/mock"
)
// GalleryReaderWriter is an autogenerated mock type for the GalleryReaderWriter type
@@ -16,6 +14,41 @@ type GalleryReaderWriter struct {
mock.Mock
}
// AddFileID provides a mock function with given fields: ctx, id, fileID
func (_m *GalleryReaderWriter) AddFileID(ctx context.Context, id int, fileID models.FileID) error {
ret := _m.Called(ctx, id, fileID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, models.FileID) error); ok {
r0 = rf(ctx, id, fileID)
} else {
r0 = ret.Error(0)
}
return r0
}
// AddImages provides a mock function with given fields: ctx, galleryID, imageIDs
func (_m *GalleryReaderWriter) AddImages(ctx context.Context, galleryID int, imageIDs ...int) error {
_va := make([]interface{}, len(imageIDs))
for _i := range imageIDs {
_va[_i] = imageIDs[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, galleryID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, ...int) error); ok {
r0 = rf(ctx, galleryID, imageIDs...)
} else {
r0 = ret.Error(0)
}
return r0
}
// All provides a mock function with given fields: ctx
func (_m *GalleryReaderWriter) All(ctx context.Context) ([]*models.Gallery, error) {
ret := _m.Called(ctx)
@@ -60,12 +93,33 @@ func (_m *GalleryReaderWriter) Count(ctx context.Context) (int, error) {
return r0, r1
}
// CountByFileID provides a mock function with given fields: ctx, fileID
func (_m *GalleryReaderWriter) CountByFileID(ctx context.Context, fileID models.FileID) (int, error) {
ret := _m.Called(ctx, fileID)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) int); ok {
r0 = rf(ctx, fileID)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Create provides a mock function with given fields: ctx, newGallery, fileIDs
func (_m *GalleryReaderWriter) Create(ctx context.Context, newGallery *models.Gallery, fileIDs []file.ID) error {
func (_m *GalleryReaderWriter) Create(ctx context.Context, newGallery *models.Gallery, fileIDs []models.FileID) error {
ret := _m.Called(ctx, newGallery, fileIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Gallery, []file.ID) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, *models.Gallery, []models.FileID) error); ok {
r0 = rf(ctx, newGallery, fileIDs)
} else {
r0 = ret.Error(0)
@@ -157,6 +211,75 @@ func (_m *GalleryReaderWriter) FindByChecksums(ctx context.Context, checksums []
return r0, r1
}
// FindByFileID provides a mock function with given fields: ctx, fileID
func (_m *GalleryReaderWriter) FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error) {
ret := _m.Called(ctx, fileID)
var r0 []*models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.Gallery); ok {
r0 = rf(ctx, fileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Gallery)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFingerprints provides a mock function with given fields: ctx, fp
func (_m *GalleryReaderWriter) FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Gallery, error) {
ret := _m.Called(ctx, fp)
var r0 []*models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, []models.Fingerprint) []*models.Gallery); ok {
r0 = rf(ctx, fp)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Gallery)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []models.Fingerprint) error); ok {
r1 = rf(ctx, fp)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFolderID provides a mock function with given fields: ctx, folderID
func (_m *GalleryReaderWriter) FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Gallery, error) {
ret := _m.Called(ctx, folderID)
var r0 []*models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, models.FolderID) []*models.Gallery); ok {
r0 = rf(ctx, folderID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Gallery)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FolderID) error); ok {
r1 = rf(ctx, folderID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByImageID provides a mock function with given fields: ctx, imageID
func (_m *GalleryReaderWriter) FindByImageID(ctx context.Context, imageID int) ([]*models.Gallery, error) {
ret := _m.Called(ctx, imageID)
@@ -249,13 +372,59 @@ func (_m *GalleryReaderWriter) FindMany(ctx context.Context, ids []int) ([]*mode
return r0, r1
}
// GetImageIDs provides a mock function with given fields: ctx, galleryID
func (_m *GalleryReaderWriter) GetImageIDs(ctx context.Context, galleryID int) ([]int, error) {
ret := _m.Called(ctx, galleryID)
// FindUserGalleryByTitle provides a mock function with given fields: ctx, title
func (_m *GalleryReaderWriter) FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error) {
ret := _m.Called(ctx, title)
var r0 []*models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Gallery); ok {
r0 = rf(ctx, title)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Gallery)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, title)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, relatedID
func (_m *GalleryReaderWriter) GetFiles(ctx context.Context, relatedID int) ([]models.File, error) {
ret := _m.Called(ctx, relatedID)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, int) []models.File); ok {
r0 = rf(ctx, relatedID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, relatedID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetImageIDs provides a mock function with given fields: ctx, relatedID
func (_m *GalleryReaderWriter) GetImageIDs(ctx context.Context, relatedID int) ([]int, error) {
ret := _m.Called(ctx, relatedID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, galleryID)
r0 = rf(ctx, relatedID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
@@ -264,7 +433,30 @@ func (_m *GalleryReaderWriter) GetImageIDs(ctx context.Context, galleryID int) (
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, galleryID)
r1 = rf(ctx, relatedID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetManyFileIDs provides a mock function with given fields: ctx, ids
func (_m *GalleryReaderWriter) GetManyFileIDs(ctx context.Context, ids []int) ([][]models.FileID, error) {
ret := _m.Called(ctx, ids)
var r0 [][]models.FileID
if rf, ok := ret.Get(0).(func(context.Context, []int) [][]models.FileID); ok {
r0 = rf(ctx, ids)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([][]models.FileID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []int) error); ok {
r1 = rf(ctx, ids)
} else {
r1 = ret.Error(1)
}
@@ -392,6 +584,27 @@ func (_m *GalleryReaderWriter) QueryCount(ctx context.Context, galleryFilter *mo
return r0, r1
}
// RemoveImages provides a mock function with given fields: ctx, galleryID, imageIDs
func (_m *GalleryReaderWriter) RemoveImages(ctx context.Context, galleryID int, imageIDs ...int) error {
_va := make([]interface{}, len(imageIDs))
for _i := range imageIDs {
_va[_i] = imageIDs[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, galleryID)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, ...int) error); ok {
r0 = rf(ctx, galleryID, imageIDs...)
} else {
r0 = ret.Error(0)
}
return r0
}
// Update provides a mock function with given fields: ctx, updatedGallery
func (_m *GalleryReaderWriter) Update(ctx context.Context, updatedGallery *models.Gallery) error {
ret := _m.Called(ctx, updatedGallery)

View File

@@ -14,6 +14,20 @@ type ImageReaderWriter struct {
mock.Mock
}
// AddFileID provides a mock function with given fields: ctx, id, fileID
func (_m *ImageReaderWriter) AddFileID(ctx context.Context, id int, fileID models.FileID) error {
ret := _m.Called(ctx, id, fileID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, models.FileID) error); ok {
r0 = rf(ctx, id, fileID)
} else {
r0 = ret.Error(0)
}
return r0
}
// All provides a mock function with given fields: ctx
func (_m *ImageReaderWriter) All(ctx context.Context) ([]*models.Image, error) {
ret := _m.Called(ctx)
@@ -58,6 +72,27 @@ func (_m *ImageReaderWriter) Count(ctx context.Context) (int, error) {
return r0, r1
}
// CountByFileID provides a mock function with given fields: ctx, fileID
func (_m *ImageReaderWriter) CountByFileID(ctx context.Context, fileID models.FileID) (int, error) {
ret := _m.Called(ctx, fileID)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) int); ok {
r0 = rf(ctx, fileID)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CountByGalleryID provides a mock function with given fields: ctx, galleryID
func (_m *ImageReaderWriter) CountByGalleryID(ctx context.Context, galleryID int) (int, error) {
ret := _m.Called(ctx, galleryID)
@@ -174,6 +209,75 @@ func (_m *ImageReaderWriter) FindByChecksum(ctx context.Context, checksum string
return r0, r1
}
// FindByFileID provides a mock function with given fields: ctx, fileID
func (_m *ImageReaderWriter) FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Image, error) {
ret := _m.Called(ctx, fileID)
var r0 []*models.Image
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.Image); ok {
r0 = rf(ctx, fileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFingerprints provides a mock function with given fields: ctx, fp
func (_m *ImageReaderWriter) FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Image, error) {
ret := _m.Called(ctx, fp)
var r0 []*models.Image
if rf, ok := ret.Get(0).(func(context.Context, []models.Fingerprint) []*models.Image); ok {
r0 = rf(ctx, fp)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []models.Fingerprint) error); ok {
r1 = rf(ctx, fp)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFolderID provides a mock function with given fields: ctx, fileID
func (_m *ImageReaderWriter) FindByFolderID(ctx context.Context, fileID models.FolderID) ([]*models.Image, error) {
ret := _m.Called(ctx, fileID)
var r0 []*models.Image
if rf, ok := ret.Get(0).(func(context.Context, models.FolderID) []*models.Image); ok {
r0 = rf(ctx, fileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FolderID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByGalleryID provides a mock function with given fields: ctx, galleryID
func (_m *ImageReaderWriter) FindByGalleryID(ctx context.Context, galleryID int) ([]*models.Image, error) {
ret := _m.Called(ctx, galleryID)
@@ -197,6 +301,29 @@ func (_m *ImageReaderWriter) FindByGalleryID(ctx context.Context, galleryID int)
return r0, r1
}
// FindByZipFileID provides a mock function with given fields: ctx, zipFileID
func (_m *ImageReaderWriter) FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]*models.Image, error) {
ret := _m.Called(ctx, zipFileID)
var r0 []*models.Image
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.Image); ok {
r0 = rf(ctx, zipFileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, zipFileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindMany provides a mock function with given fields: ctx, ids
func (_m *ImageReaderWriter) FindMany(ctx context.Context, ids []int) ([]*models.Image, error) {
ret := _m.Called(ctx, ids)
@@ -220,6 +347,29 @@ func (_m *ImageReaderWriter) FindMany(ctx context.Context, ids []int) ([]*models
return r0, r1
}
// GetFiles provides a mock function with given fields: ctx, relatedID
func (_m *ImageReaderWriter) GetFiles(ctx context.Context, relatedID int) ([]models.File, error) {
ret := _m.Called(ctx, relatedID)
var r0 []models.File
if rf, ok := ret.Get(0).(func(context.Context, int) []models.File); ok {
r0 = rf(ctx, relatedID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, relatedID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetGalleryIDs provides a mock function with given fields: ctx, relatedID
func (_m *ImageReaderWriter) GetGalleryIDs(ctx context.Context, relatedID int) ([]int, error) {
ret := _m.Called(ctx, relatedID)
@@ -243,6 +393,29 @@ func (_m *ImageReaderWriter) GetGalleryIDs(ctx context.Context, relatedID int) (
return r0, r1
}
// GetManyFileIDs provides a mock function with given fields: ctx, ids
func (_m *ImageReaderWriter) GetManyFileIDs(ctx context.Context, ids []int) ([][]models.FileID, error) {
ret := _m.Called(ctx, ids)
var r0 [][]models.FileID
if rf, ok := ret.Get(0).(func(context.Context, []int) [][]models.FileID); ok {
r0 = rf(ctx, ids)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([][]models.FileID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []int) error); ok {
r1 = rf(ctx, ids)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetPerformerIDs provides a mock function with given fields: ctx, relatedID
func (_m *ImageReaderWriter) GetPerformerIDs(ctx context.Context, relatedID int) ([]int, error) {
ret := _m.Called(ctx, relatedID)
@@ -453,3 +626,31 @@ func (_m *ImageReaderWriter) UpdatePartial(ctx context.Context, id int, partial
return r0, r1
}
// UpdatePerformers provides a mock function with given fields: ctx, imageID, performerIDs
func (_m *ImageReaderWriter) UpdatePerformers(ctx context.Context, imageID int, performerIDs []int) error {
ret := _m.Called(ctx, imageID, performerIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, imageID, performerIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateTags provides a mock function with given fields: ctx, imageID, tagIDs
func (_m *ImageReaderWriter) UpdateTags(ctx context.Context, imageID int, tagIDs []int) error {
ret := _m.Called(ctx, imageID, tagIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, imageID, tagIDs)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -199,13 +199,13 @@ func (_m *SceneMarkerReaderWriter) GetMarkerStrings(ctx context.Context, q *stri
return r0, r1
}
// GetTagIDs provides a mock function with given fields: ctx, imageID
func (_m *SceneMarkerReaderWriter) GetTagIDs(ctx context.Context, imageID int) ([]int, error) {
ret := _m.Called(ctx, imageID)
// GetTagIDs provides a mock function with given fields: ctx, relatedID
func (_m *SceneMarkerReaderWriter) GetTagIDs(ctx context.Context, relatedID int) ([]int, error) {
ret := _m.Called(ctx, relatedID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, imageID)
r0 = rf(ctx, relatedID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
@@ -214,7 +214,7 @@ func (_m *SceneMarkerReaderWriter) GetTagIDs(ctx context.Context, imageID int) (
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, imageID)
r1 = rf(ctx, relatedID)
} else {
r1 = ret.Error(1)
}

View File

@@ -5,10 +5,8 @@ package mocks
import (
context "context"
file "github.com/stashapp/stash/pkg/file"
mock "github.com/stretchr/testify/mock"
models "github.com/stashapp/stash/pkg/models"
mock "github.com/stretchr/testify/mock"
)
// SceneReaderWriter is an autogenerated mock type for the SceneReaderWriter type
@@ -16,6 +14,34 @@ type SceneReaderWriter struct {
mock.Mock
}
// AddFileID provides a mock function with given fields: ctx, id, fileID
func (_m *SceneReaderWriter) AddFileID(ctx context.Context, id int, fileID models.FileID) error {
ret := _m.Called(ctx, id, fileID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, models.FileID) error); ok {
r0 = rf(ctx, id, fileID)
} else {
r0 = ret.Error(0)
}
return r0
}
// AddGalleryIDs provides a mock function with given fields: ctx, sceneID, galleryIDs
func (_m *SceneReaderWriter) AddGalleryIDs(ctx context.Context, sceneID int, galleryIDs []int) error {
ret := _m.Called(ctx, sceneID, galleryIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, sceneID, galleryIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// All provides a mock function with given fields: ctx
func (_m *SceneReaderWriter) All(ctx context.Context) ([]*models.Scene, error) {
ret := _m.Called(ctx)
@@ -39,6 +65,20 @@ func (_m *SceneReaderWriter) All(ctx context.Context) ([]*models.Scene, error) {
return r0, r1
}
// AssignFiles provides a mock function with given fields: ctx, sceneID, fileID
func (_m *SceneReaderWriter) AssignFiles(ctx context.Context, sceneID int, fileID []models.FileID) error {
ret := _m.Called(ctx, sceneID, fileID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []models.FileID) error); ok {
r0 = rf(ctx, sceneID, fileID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Count provides a mock function with given fields: ctx
func (_m *SceneReaderWriter) Count(ctx context.Context) (int, error) {
ret := _m.Called(ctx)
@@ -60,6 +100,27 @@ func (_m *SceneReaderWriter) Count(ctx context.Context) (int, error) {
return r0, r1
}
// CountByFileID provides a mock function with given fields: ctx, fileID
func (_m *SceneReaderWriter) CountByFileID(ctx context.Context, fileID models.FileID) (int, error) {
ret := _m.Called(ctx, fileID)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) int); ok {
r0 = rf(ctx, fileID)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CountByMovieID provides a mock function with given fields: ctx, movieID
func (_m *SceneReaderWriter) CountByMovieID(ctx context.Context, movieID int) (int, error) {
ret := _m.Called(ctx, movieID)
@@ -187,11 +248,11 @@ func (_m *SceneReaderWriter) CountMissingOSHash(ctx context.Context) (int, error
}
// Create provides a mock function with given fields: ctx, newScene, fileIDs
func (_m *SceneReaderWriter) Create(ctx context.Context, newScene *models.Scene, fileIDs []file.ID) error {
func (_m *SceneReaderWriter) Create(ctx context.Context, newScene *models.Scene, fileIDs []models.FileID) error {
ret := _m.Called(ctx, newScene, fileIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Scene, []file.ID) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, *models.Scene, []models.FileID) error); ok {
r0 = rf(ctx, newScene, fileIDs)
} else {
r0 = ret.Error(0)
@@ -302,6 +363,52 @@ func (_m *SceneReaderWriter) FindByChecksum(ctx context.Context, checksum string
return r0, r1
}
// FindByFileID provides a mock function with given fields: ctx, fileID
func (_m *SceneReaderWriter) FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Scene, error) {
ret := _m.Called(ctx, fileID)
var r0 []*models.Scene
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.Scene); ok {
r0 = rf(ctx, fileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Scene)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByFingerprints provides a mock function with given fields: ctx, fp
func (_m *SceneReaderWriter) FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Scene, error) {
ret := _m.Called(ctx, fp)
var r0 []*models.Scene
if rf, ok := ret.Get(0).(func(context.Context, []models.Fingerprint) []*models.Scene); ok {
r0 = rf(ctx, fp)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Scene)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []models.Fingerprint) error); ok {
r1 = rf(ctx, fp)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByGalleryID provides a mock function with given fields: ctx, performerID
func (_m *SceneReaderWriter) FindByGalleryID(ctx context.Context, performerID int) ([]*models.Scene, error) {
ret := _m.Called(ctx, performerID)
@@ -417,6 +524,29 @@ func (_m *SceneReaderWriter) FindByPerformerID(ctx context.Context, performerID
return r0, r1
}
// FindByPrimaryFileID provides a mock function with given fields: ctx, fileID
func (_m *SceneReaderWriter) FindByPrimaryFileID(ctx context.Context, fileID models.FileID) ([]*models.Scene, error) {
ret := _m.Called(ctx, fileID)
var r0 []*models.Scene
if rf, ok := ret.Get(0).(func(context.Context, models.FileID) []*models.Scene); ok {
r0 = rf(ctx, fileID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Scene)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.FileID) error); ok {
r1 = rf(ctx, fileID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindDuplicates provides a mock function with given fields: ctx, distance, durationDiff
func (_m *SceneReaderWriter) FindDuplicates(ctx context.Context, distance int, durationDiff float64) ([][]*models.Scene, error) {
ret := _m.Called(ctx, distance, durationDiff)
@@ -487,15 +617,15 @@ func (_m *SceneReaderWriter) GetCover(ctx context.Context, sceneID int) ([]byte,
}
// GetFiles provides a mock function with given fields: ctx, relatedID
func (_m *SceneReaderWriter) GetFiles(ctx context.Context, relatedID int) ([]*file.VideoFile, error) {
func (_m *SceneReaderWriter) GetFiles(ctx context.Context, relatedID int) ([]*models.VideoFile, error) {
ret := _m.Called(ctx, relatedID)
var r0 []*file.VideoFile
if rf, ok := ret.Get(0).(func(context.Context, int) []*file.VideoFile); ok {
var r0 []*models.VideoFile
if rf, ok := ret.Get(0).(func(context.Context, int) []*models.VideoFile); ok {
r0 = rf(ctx, relatedID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*file.VideoFile)
r0 = ret.Get(0).([]*models.VideoFile)
}
}
@@ -532,6 +662,29 @@ func (_m *SceneReaderWriter) GetGalleryIDs(ctx context.Context, relatedID int) (
return r0, r1
}
// GetManyFileIDs provides a mock function with given fields: ctx, ids
func (_m *SceneReaderWriter) GetManyFileIDs(ctx context.Context, ids []int) ([][]models.FileID, error) {
ret := _m.Called(ctx, ids)
var r0 [][]models.FileID
if rf, ok := ret.Get(0).(func(context.Context, []int) [][]models.FileID); ok {
r0 = rf(ctx, ids)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([][]models.FileID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, []int) error); ok {
r1 = rf(ctx, ids)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMovies provides a mock function with given fields: ctx, id
func (_m *SceneReaderWriter) GetMovies(ctx context.Context, id int) ([]models.MoviesScenes, error) {
ret := _m.Called(ctx, id)
@@ -689,20 +842,20 @@ func (_m *SceneReaderWriter) IncrementOCounter(ctx context.Context, id int) (int
return r0, r1
}
// IncrementWatchCount provides a mock function with given fields: ctx, id
func (_m *SceneReaderWriter) IncrementWatchCount(ctx context.Context, id int) (int, error) {
ret := _m.Called(ctx, id)
// IncrementWatchCount provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) IncrementWatchCount(ctx context.Context, sceneID int) (int, error) {
ret := _m.Called(ctx, sceneID)
var r0 int
if rf, ok := ret.Get(0).(func(context.Context, int) int); ok {
r0 = rf(ctx, id)
r0 = rf(ctx, sceneID)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, id)
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
@@ -859,20 +1012,20 @@ func (_m *SceneReaderWriter) ResetOCounter(ctx context.Context, id int) (int, er
return r0, r1
}
// SaveActivity provides a mock function with given fields: ctx, id, resumeTime, playDuration
func (_m *SceneReaderWriter) SaveActivity(ctx context.Context, id int, resumeTime *float64, playDuration *float64) (bool, error) {
ret := _m.Called(ctx, id, resumeTime, playDuration)
// SaveActivity provides a mock function with given fields: ctx, sceneID, resumeTime, playDuration
func (_m *SceneReaderWriter) SaveActivity(ctx context.Context, sceneID int, resumeTime *float64, playDuration *float64) (bool, error) {
ret := _m.Called(ctx, sceneID, resumeTime, playDuration)
var r0 bool
if rf, ok := ret.Get(0).(func(context.Context, int, *float64, *float64) bool); ok {
r0 = rf(ctx, id, resumeTime, playDuration)
r0 = rf(ctx, sceneID, resumeTime, playDuration)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int, *float64, *float64) error); ok {
r1 = rf(ctx, id, resumeTime, playDuration)
r1 = rf(ctx, sceneID, resumeTime, playDuration)
} else {
r1 = ret.Error(1)
}

View File

@@ -58,13 +58,13 @@ func (_m *StudioReaderWriter) Count(ctx context.Context) (int, error) {
return r0, r1
}
// Create provides a mock function with given fields: ctx, input
func (_m *StudioReaderWriter) Create(ctx context.Context, input *models.Studio) error {
ret := _m.Called(ctx, input)
// Create provides a mock function with given fields: ctx, newStudio
func (_m *StudioReaderWriter) Create(ctx context.Context, newStudio *models.Studio) error {
ret := _m.Called(ctx, newStudio)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Studio) error); ok {
r0 = rf(ctx, input)
r0 = rf(ctx, newStudio)
} else {
r0 = ret.Error(0)
}
@@ -132,6 +132,29 @@ func (_m *StudioReaderWriter) FindByName(ctx context.Context, name string, nocas
return r0, r1
}
// FindBySceneID provides a mock function with given fields: ctx, sceneID
func (_m *StudioReaderWriter) FindBySceneID(ctx context.Context, sceneID int) (*models.Studio, error) {
ret := _m.Called(ctx, sceneID)
var r0 *models.Studio
if rf, ok := ret.Get(0).(func(context.Context, int) *models.Studio); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Studio)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindByStashID provides a mock function with given fields: ctx, stashID
func (_m *StudioReaderWriter) FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Studio, error) {
ret := _m.Called(ctx, stashID)
@@ -395,13 +418,13 @@ func (_m *StudioReaderWriter) UpdateImage(ctx context.Context, studioID int, ima
return r0
}
// UpdatePartial provides a mock function with given fields: ctx, input
func (_m *StudioReaderWriter) UpdatePartial(ctx context.Context, input models.StudioPartial) (*models.Studio, error) {
ret := _m.Called(ctx, input)
// UpdatePartial provides a mock function with given fields: ctx, updatedStudio
func (_m *StudioReaderWriter) UpdatePartial(ctx context.Context, updatedStudio models.StudioPartial) (*models.Studio, error) {
ret := _m.Called(ctx, updatedStudio)
var r0 *models.Studio
if rf, ok := ret.Get(0).(func(context.Context, models.StudioPartial) *models.Studio); ok {
r0 = rf(ctx, input)
r0 = rf(ctx, updatedStudio)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Studio)
@@ -410,7 +433,7 @@ func (_m *StudioReaderWriter) UpdatePartial(ctx context.Context, input models.St
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.StudioPartial) error); ok {
r1 = rf(ctx, input)
r1 = rf(ctx, updatedStudio)
} else {
r1 = ret.Error(1)
}

View File

@@ -385,13 +385,13 @@ func (_m *TagReaderWriter) FindMany(ctx context.Context, ids []int) ([]*models.T
return r0, r1
}
// GetAliases provides a mock function with given fields: ctx, tagID
func (_m *TagReaderWriter) GetAliases(ctx context.Context, tagID int) ([]string, error) {
ret := _m.Called(ctx, tagID)
// GetAliases provides a mock function with given fields: ctx, relatedID
func (_m *TagReaderWriter) GetAliases(ctx context.Context, relatedID int) ([]string, error) {
ret := _m.Called(ctx, relatedID)
var r0 []string
if rf, ok := ret.Get(0).(func(context.Context, int) []string); ok {
r0 = rf(ctx, tagID)
r0 = rf(ctx, relatedID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
@@ -400,7 +400,7 @@ func (_m *TagReaderWriter) GetAliases(ctx context.Context, tagID int) ([]string,
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, tagID)
r1 = rf(ctx, relatedID)
} else {
r1 = ret.Error(1)
}

View File

@@ -31,6 +31,10 @@ type imageResolver struct {
images []*models.Image
}
func (s *imageResolver) Find(ctx context.Context, id int) (*models.Image, error) {
panic("not implemented")
}
func (s *imageResolver) FindMany(ctx context.Context, ids []int) ([]*models.Image, error) {
return s.images, nil
}

View File

@@ -1,9 +1,14 @@
package models
import (
"bytes"
"fmt"
"io"
"io/fs"
"math"
"net/http"
"strconv"
"time"
)
type HashAlgorithm string
@@ -47,3 +52,244 @@ func (e *HashAlgorithm) UnmarshalGQL(v interface{}) error {
func (e HashAlgorithm) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
// ID represents an ID of a file.
type FileID int32
func (i FileID) String() string {
return strconv.Itoa(int(i))
}
func (i *FileID) UnmarshalGQL(v interface{}) (err error) {
switch v := v.(type) {
case string:
var id int
id, err = strconv.Atoi(v)
*i = FileID(id)
return err
case int:
*i = FileID(v)
return nil
default:
return fmt.Errorf("%T is not an int", v)
}
}
func (i FileID) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(i.String()))
}
// DirEntry represents a file or directory in the file system.
type DirEntry struct {
ZipFileID *FileID `json:"zip_file_id"`
// transient - not persisted
// only guaranteed to have id, path and basename set
ZipFile File
ModTime time.Time `json:"mod_time"`
}
func (e *DirEntry) info(fs FS, path string) (fs.FileInfo, error) {
if e.ZipFile != nil {
zipPath := e.ZipFile.Base().Path
zfs, err := fs.OpenZip(zipPath)
if err != nil {
return nil, err
}
defer zfs.Close()
fs = zfs
}
// else assume os file
ret, err := fs.Lstat(path)
return ret, err
}
// File represents a file in the file system.
type File interface {
Base() *BaseFile
SetFingerprints(fp Fingerprints)
Open(fs FS) (io.ReadCloser, error)
}
// BaseFile represents a file in the file system.
type BaseFile struct {
ID FileID `json:"id"`
DirEntry
// resolved from parent folder and basename only - not stored in DB
Path string `json:"path"`
Basename string `json:"basename"`
ParentFolderID FolderID `json:"parent_folder_id"`
Fingerprints Fingerprints `json:"fingerprints"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (f *BaseFile) FingerprintSlice() []Fingerprint {
return f.Fingerprints
}
// SetFingerprints sets the fingerprints of the file.
// If a fingerprint of the same type already exists, it is overwritten.
func (f *BaseFile) SetFingerprints(fp Fingerprints) {
for _, v := range fp {
f.SetFingerprint(v)
}
}
// SetFingerprint sets the fingerprint of the file.
// If a fingerprint of the same type already exists, it is overwritten.
func (f *BaseFile) SetFingerprint(fp Fingerprint) {
for i, existing := range f.Fingerprints {
if existing.Type == fp.Type {
f.Fingerprints[i] = fp
return
}
}
f.Fingerprints = append(f.Fingerprints, fp)
}
// Base is used to fulfil the File interface.
func (f *BaseFile) Base() *BaseFile {
return f
}
func (f *BaseFile) Open(fs FS) (io.ReadCloser, error) {
if f.ZipFile != nil {
zipPath := f.ZipFile.Base().Path
zfs, err := fs.OpenZip(zipPath)
if err != nil {
return nil, err
}
return zfs.OpenOnly(f.Path)
}
return fs.Open(f.Path)
}
func (f *BaseFile) Info(fs FS) (fs.FileInfo, error) {
return f.info(fs, f.Path)
}
func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) error {
reader, err := f.Open(fs)
if err != nil {
return err
}
defer reader.Close()
content, ok := reader.(io.ReadSeeker)
if !ok {
data, err := io.ReadAll(reader)
if err != nil {
return err
}
content = bytes.NewReader(data)
}
if r.URL.Query().Has("t") {
w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
} else {
w.Header().Set("Cache-Control", "no-cache")
}
http.ServeContent(w, r, f.Basename, f.ModTime, content)
return nil
}
// VisualFile is an interface for files that have a width and height.
type VisualFile interface {
File
GetWidth() int
GetHeight() int
GetFormat() string
}
func GetMinResolution(f VisualFile) int {
w := f.GetWidth()
h := f.GetHeight()
if w < h {
return w
}
return h
}
// ImageFile is an extension of BaseFile to represent image files.
type ImageFile struct {
*BaseFile
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
}
func (f ImageFile) GetWidth() int {
return f.Width
}
func (f ImageFile) GetHeight() int {
return f.Height
}
func (f ImageFile) GetFormat() string {
return f.Format
}
// VideoFile is an extension of BaseFile to represent video files.
type VideoFile struct {
*BaseFile
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Duration float64 `json:"duration"`
VideoCodec string `json:"video_codec"`
AudioCodec string `json:"audio_codec"`
FrameRate float64 `json:"frame_rate"`
BitRate int64 `json:"bitrate"`
Interactive bool `json:"interactive"`
InteractiveSpeed *int `json:"interactive_speed"`
}
func (f VideoFile) GetWidth() int {
return f.Width
}
func (f VideoFile) GetHeight() int {
return f.Height
}
func (f VideoFile) GetFormat() string {
return f.Format
}
// #1572 - Inf and NaN values cause the JSON marshaller to fail
// Replace these values with 0 rather than erroring
func (f VideoFile) DurationFinite() float64 {
ret := f.Duration
if math.IsInf(ret, 0) || math.IsNaN(ret) {
return 0
}
return ret
}
func (f VideoFile) FrameRateFinite() float64 {
ret := f.FrameRate
if math.IsInf(ret, 0) || math.IsNaN(ret) {
return 0
}
return ret
}

View File

@@ -0,0 +1,51 @@
package models
import (
"fmt"
"io"
"io/fs"
"strconv"
"time"
)
// FolderID represents an ID of a folder.
type FolderID int32
// String converts the ID to a string.
func (i FolderID) String() string {
return strconv.Itoa(int(i))
}
func (i *FolderID) UnmarshalGQL(v interface{}) (err error) {
switch v := v.(type) {
case string:
var id int
id, err = strconv.Atoi(v)
*i = FolderID(id)
return err
case int:
*i = FolderID(v)
return nil
default:
return fmt.Errorf("%T is not an int", v)
}
}
func (i FolderID) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(i.String()))
}
// Folder represents a folder in the file system.
type Folder struct {
ID FolderID `json:"id"`
DirEntry
Path string `json:"path"`
ParentFolderID *FolderID `json:"parent_folder_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (f *Folder) Info(fs FS) (fs.FileInfo, error) {
return f.info(fs, f.Path)
}

View File

@@ -5,8 +5,6 @@ import (
"path/filepath"
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
)
type Gallery struct {
@@ -24,11 +22,11 @@ type Gallery struct {
// transient - not persisted
Files RelatedFiles
// transient - not persisted
PrimaryFileID *file.ID
PrimaryFileID *FileID
// transient - path of primary file or folder
Path string
FolderID *file.FolderID `json:"folder_id"`
FolderID *FolderID `json:"folder_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
@@ -45,13 +43,13 @@ func (g *Gallery) IsUserCreated() bool {
}
func (g *Gallery) LoadFiles(ctx context.Context, l FileLoader) error {
return g.Files.load(func() ([]file.File, error) {
return g.Files.load(func() ([]File, error) {
return l.GetFiles(ctx, g.ID)
})
}
func (g *Gallery) LoadPrimaryFile(ctx context.Context, l file.Finder) error {
return g.Files.loadPrimary(func() (file.File, error) {
func (g *Gallery) LoadPrimaryFile(ctx context.Context, l FileGetter) error {
return g.Files.loadPrimary(func() (File, error) {
if g.PrimaryFileID == nil {
return nil, nil
}
@@ -89,7 +87,7 @@ func (g *Gallery) LoadTagIDs(ctx context.Context, l TagIDLoader) error {
func (g Gallery) PrimaryChecksum() string {
// renamed from Checksum to prevent gqlgen from using it in the resolver
if p := g.Files.Primary(); p != nil {
v := p.Base().Fingerprints.Get(file.FingerprintTypeMD5)
v := p.Base().Fingerprints.Get(FingerprintTypeMD5)
if v == nil {
return ""
}
@@ -120,7 +118,7 @@ type GalleryPartial struct {
SceneIDs *UpdateIDs
TagIDs *UpdateIDs
PerformerIDs *UpdateIDs
PrimaryFileID *file.ID
PrimaryFileID *FileID
}
func NewGalleryPartial() GalleryPartial {

View File

@@ -5,8 +5,6 @@ import (
"path/filepath"
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
)
// Image stores the metadata for a single image.
@@ -24,7 +22,7 @@ type Image struct {
// transient - not persisted
Files RelatedFiles
PrimaryFileID *file.ID
PrimaryFileID *FileID
// transient - path of primary file - empty if no files
Path string
// transient - checksum of primary file - empty if no files
@@ -39,13 +37,13 @@ type Image struct {
}
func (i *Image) LoadFiles(ctx context.Context, l FileLoader) error {
return i.Files.load(func() ([]file.File, error) {
return i.Files.load(func() ([]File, error) {
return l.GetFiles(ctx, i.ID)
})
}
func (i *Image) LoadPrimaryFile(ctx context.Context, l file.Finder) error {
return i.Files.loadPrimary(func() (file.File, error) {
func (i *Image) LoadPrimaryFile(ctx context.Context, l FileGetter) error {
return i.Files.loadPrimary(func() (File, error) {
if i.PrimaryFileID == nil {
return nil, nil
}
@@ -107,7 +105,7 @@ func (i Image) DisplayName() string {
type ImageCreateInput struct {
*Image
FileIDs []file.ID
FileIDs []FileID
}
type ImagePartial struct {
@@ -125,7 +123,7 @@ type ImagePartial struct {
GalleryIDs *UpdateIDs
TagIDs *UpdateIDs
PerformerIDs *UpdateIDs
PrimaryFileID *file.ID
PrimaryFileID *FileID
}
func NewImagePartial() ImagePartial {

View File

@@ -6,8 +6,6 @@ import (
"path/filepath"
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
)
// Scene stores the metadata for a single video scene.
@@ -26,7 +24,7 @@ type Scene struct {
// transient - not persisted
Files RelatedVideoFiles
PrimaryFileID *file.ID
PrimaryFileID *FileID
// transient - path of primary file - empty if no files
Path string
// transient - oshash of primary file - empty if no files
@@ -57,13 +55,13 @@ func (s *Scene) LoadURLs(ctx context.Context, l URLLoader) error {
}
func (s *Scene) LoadFiles(ctx context.Context, l VideoFileLoader) error {
return s.Files.load(func() ([]*file.VideoFile, error) {
return s.Files.load(func() ([]*VideoFile, error) {
return l.GetFiles(ctx, s.ID)
})
}
func (s *Scene) LoadPrimaryFile(ctx context.Context, l file.Finder) error {
return s.Files.loadPrimary(func() (*file.VideoFile, error) {
func (s *Scene) LoadPrimaryFile(ctx context.Context, l FileGetter) error {
return s.Files.loadPrimary(func() (*VideoFile, error) {
if s.PrimaryFileID == nil {
return nil, nil
}
@@ -73,10 +71,10 @@ func (s *Scene) LoadPrimaryFile(ctx context.Context, l file.Finder) error {
return nil, err
}
var vf *file.VideoFile
var vf *VideoFile
if len(f) > 0 {
var ok bool
vf, ok = f[0].(*file.VideoFile)
vf, ok = f[0].(*VideoFile)
if !ok {
return nil, errors.New("not a video file")
}
@@ -173,7 +171,7 @@ type ScenePartial struct {
PerformerIDs *UpdateIDs
MovieIDs *UpdateMovieIDs
StashIDs *UpdateStashIDs
PrimaryFileID *file.ID
PrimaryFileID *FileID
}
func NewScenePartial() ScenePartial {

View File

@@ -1,7 +1,5 @@
package models
import "context"
type MovieFilterType struct {
Name *StringCriterionInput `json:"name"`
Director *StringCriterionInput `json:"director"`
@@ -27,37 +25,3 @@ type MovieFilterType struct {
// Filter by updated at
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
}
type MovieReader interface {
Find(ctx context.Context, id int) (*Movie, error)
FindMany(ctx context.Context, ids []int) ([]*Movie, error)
// FindBySceneID(sceneID int) ([]*Movie, error)
FindByName(ctx context.Context, name string, nocase bool) (*Movie, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Movie, error)
All(ctx context.Context) ([]*Movie, error)
Count(ctx context.Context) (int, error)
Query(ctx context.Context, movieFilter *MovieFilterType, findFilter *FindFilterType) ([]*Movie, int, error)
QueryCount(ctx context.Context, movieFilter *MovieFilterType, findFilter *FindFilterType) (int, error)
GetFrontImage(ctx context.Context, movieID int) ([]byte, error)
HasFrontImage(ctx context.Context, movieID int) (bool, error)
GetBackImage(ctx context.Context, movieID int) ([]byte, error)
HasBackImage(ctx context.Context, movieID int) (bool, error)
FindByPerformerID(ctx context.Context, performerID int) ([]*Movie, error)
CountByPerformerID(ctx context.Context, performerID int) (int, error)
FindByStudioID(ctx context.Context, studioID int) ([]*Movie, error)
CountByStudioID(ctx context.Context, studioID int) (int, error)
}
type MovieWriter interface {
Create(ctx context.Context, newMovie *Movie) error
UpdatePartial(ctx context.Context, id int, updatedMovie MoviePartial) (*Movie, error)
Update(ctx context.Context, updatedMovie *Movie) error
Destroy(ctx context.Context, id int) error
UpdateFrontImage(ctx context.Context, movieID int, frontImage []byte) error
UpdateBackImage(ctx context.Context, movieID int, backImage []byte) error
}
type MovieReaderWriter interface {
MovieReader
MovieWriter
}

View File

@@ -1,7 +1,6 @@
package models
import (
"context"
"fmt"
"io"
"strconv"
@@ -193,44 +192,3 @@ type PerformerFilterType struct {
// Filter by updated at
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
}
type PerformerFinder interface {
FindMany(ctx context.Context, ids []int) ([]*Performer, error)
}
type PerformerReader interface {
Find(ctx context.Context, id int) (*Performer, error)
PerformerFinder
FindBySceneID(ctx context.Context, sceneID int) ([]*Performer, error)
FindByImageID(ctx context.Context, imageID int) ([]*Performer, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Performer, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Performer, error)
FindByStashID(ctx context.Context, stashID StashID) ([]*Performer, error)
FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*Performer, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
Count(ctx context.Context) (int, error)
All(ctx context.Context) ([]*Performer, error)
// TODO - this interface is temporary until the filter schema can fully
// support the query needed
QueryForAutoTag(ctx context.Context, words []string) ([]*Performer, error)
Query(ctx context.Context, performerFilter *PerformerFilterType, findFilter *FindFilterType) ([]*Performer, int, error)
QueryCount(ctx context.Context, galleryFilter *PerformerFilterType, findFilter *FindFilterType) (int, error)
AliasLoader
GetImage(ctx context.Context, performerID int) ([]byte, error)
HasImage(ctx context.Context, performerID int) (bool, error)
StashIDLoader
TagIDLoader
}
type PerformerWriter interface {
Create(ctx context.Context, newPerformer *Performer) error
UpdatePartial(ctx context.Context, id int, updatedPerformer PerformerPartial) (*Performer, error)
Update(ctx context.Context, updatedPerformer *Performer) error
Destroy(ctx context.Context, id int) error
UpdateImage(ctx context.Context, performerID int, image []byte) error
}
type PerformerReaderWriter interface {
PerformerReader
PerformerWriter
}

View File

@@ -1,15 +1,15 @@
package models
import (
"context"
"github.com/stashapp/stash/pkg/file"
)
import "context"
type SceneIDLoader interface {
GetSceneIDs(ctx context.Context, relatedID int) ([]int, error)
}
type ImageIDLoader interface {
GetImageIDs(ctx context.Context, relatedID int) ([]int, error)
}
type GalleryIDLoader interface {
GetGalleryIDs(ctx context.Context, relatedID int) ([]int, error)
}
@@ -22,6 +22,10 @@ type TagIDLoader interface {
GetTagIDs(ctx context.Context, relatedID int) ([]int, error)
}
type FileIDLoader interface {
GetManyFileIDs(ctx context.Context, ids []int) ([][]FileID, error)
}
type SceneMovieLoader interface {
GetMovies(ctx context.Context, id int) ([]MoviesScenes, error)
}
@@ -31,11 +35,11 @@ type StashIDLoader interface {
}
type VideoFileLoader interface {
GetFiles(ctx context.Context, relatedID int) ([]*file.VideoFile, error)
GetFiles(ctx context.Context, relatedID int) ([]*VideoFile, error)
}
type FileLoader interface {
GetFiles(ctx context.Context, relatedID int) ([]file.File, error)
GetFiles(ctx context.Context, relatedID int) ([]File, error)
}
type AliasLoader interface {
@@ -224,12 +228,12 @@ func (r *RelatedStashIDs) load(fn func() ([]StashID, error)) error {
}
type RelatedVideoFiles struct {
primaryFile *file.VideoFile
files []*file.VideoFile
primaryFile *VideoFile
files []*VideoFile
primaryLoaded bool
}
func NewRelatedVideoFiles(files []*file.VideoFile) RelatedVideoFiles {
func NewRelatedVideoFiles(files []*VideoFile) RelatedVideoFiles {
ret := RelatedVideoFiles{
files: files,
primaryLoaded: true,
@@ -242,12 +246,12 @@ func NewRelatedVideoFiles(files []*file.VideoFile) RelatedVideoFiles {
return ret
}
func (r *RelatedVideoFiles) SetPrimary(f *file.VideoFile) {
func (r *RelatedVideoFiles) SetPrimary(f *VideoFile) {
r.primaryFile = f
r.primaryLoaded = true
}
func (r *RelatedVideoFiles) Set(f []*file.VideoFile) {
func (r *RelatedVideoFiles) Set(f []*VideoFile) {
r.files = f
if len(r.files) > 0 {
r.primaryFile = r.files[0]
@@ -267,7 +271,7 @@ func (r RelatedVideoFiles) PrimaryLoaded() bool {
}
// List returns the related files. Panics if the relationship has not been loaded.
func (r RelatedVideoFiles) List() []*file.VideoFile {
func (r RelatedVideoFiles) List() []*VideoFile {
if !r.Loaded() {
panic("relationship has not been loaded")
}
@@ -276,7 +280,7 @@ func (r RelatedVideoFiles) List() []*file.VideoFile {
}
// Primary returns the primary file. Panics if the relationship has not been loaded.
func (r RelatedVideoFiles) Primary() *file.VideoFile {
func (r RelatedVideoFiles) Primary() *VideoFile {
if !r.PrimaryLoaded() {
panic("relationship has not been loaded")
}
@@ -284,7 +288,7 @@ func (r RelatedVideoFiles) Primary() *file.VideoFile {
return r.primaryFile
}
func (r *RelatedVideoFiles) load(fn func() ([]*file.VideoFile, error)) error {
func (r *RelatedVideoFiles) load(fn func() ([]*VideoFile, error)) error {
if r.Loaded() {
return nil
}
@@ -304,7 +308,7 @@ func (r *RelatedVideoFiles) load(fn func() ([]*file.VideoFile, error)) error {
return nil
}
func (r *RelatedVideoFiles) loadPrimary(fn func() (*file.VideoFile, error)) error {
func (r *RelatedVideoFiles) loadPrimary(fn func() (*VideoFile, error)) error {
if r.PrimaryLoaded() {
return nil
}
@@ -321,12 +325,12 @@ func (r *RelatedVideoFiles) loadPrimary(fn func() (*file.VideoFile, error)) erro
}
type RelatedFiles struct {
primaryFile file.File
files []file.File
primaryFile File
files []File
primaryLoaded bool
}
func NewRelatedFiles(files []file.File) RelatedFiles {
func NewRelatedFiles(files []File) RelatedFiles {
ret := RelatedFiles{
files: files,
primaryLoaded: true,
@@ -350,7 +354,7 @@ func (r RelatedFiles) PrimaryLoaded() bool {
}
// List returns the related files. Panics if the relationship has not been loaded.
func (r RelatedFiles) List() []file.File {
func (r RelatedFiles) List() []File {
if !r.Loaded() {
panic("relationship has not been loaded")
}
@@ -359,7 +363,7 @@ func (r RelatedFiles) List() []file.File {
}
// Primary returns the primary file. Panics if the relationship has not been loaded.
func (r RelatedFiles) Primary() file.File {
func (r RelatedFiles) Primary() File {
if !r.PrimaryLoaded() {
panic("relationship has not been loaded")
}
@@ -367,7 +371,7 @@ func (r RelatedFiles) Primary() file.File {
return r.primaryFile
}
func (r *RelatedFiles) load(fn func() ([]file.File, error)) error {
func (r *RelatedFiles) load(fn func() ([]File, error)) error {
if r.Loaded() {
return nil
}
@@ -387,7 +391,7 @@ func (r *RelatedFiles) load(fn func() ([]file.File, error)) error {
return nil
}
func (r *RelatedFiles) loadPrimary(fn func() (file.File, error)) error {
func (r *RelatedFiles) loadPrimary(fn func() (File, error)) error {
if r.PrimaryLoaded() {
return nil
}

View File

@@ -1,7 +1,6 @@
package models
import (
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/txn"
)
@@ -14,8 +13,8 @@ type TxnManager interface {
type Repository struct {
TxnManager
File file.Store
Folder file.FolderStore
File FileReaderWriter
Folder FolderReaderWriter
Gallery GalleryReaderWriter
GalleryChapter GalleryChapterReaderWriter
Image ImageReaderWriter

View File

@@ -0,0 +1,88 @@
package models
import (
"context"
"io/fs"
)
// FileGetter provides methods to get files by ID.
type FileGetter interface {
Find(ctx context.Context, id ...FileID) ([]File, error)
}
// FileFinder provides methods to find files.
type FileFinder interface {
FileGetter
FindAllByPath(ctx context.Context, path string) ([]File, error)
FindAllInPaths(ctx context.Context, p []string, limit, offset int) ([]File, error)
FindByPath(ctx context.Context, path string) (File, error)
FindByFingerprint(ctx context.Context, fp Fingerprint) ([]File, error)
FindByZipFileID(ctx context.Context, zipFileID FileID) ([]File, error)
FindByFileInfo(ctx context.Context, info fs.FileInfo, size int64) ([]File, error)
}
// FileQueryer provides methods to query files.
type FileQueryer interface {
Query(ctx context.Context, options FileQueryOptions) (*FileQueryResult, error)
}
// FileCounter provides methods to count files.
type FileCounter interface {
CountAllInPaths(ctx context.Context, p []string) (int, error)
CountByFolderID(ctx context.Context, folderID FolderID) (int, error)
}
// FileCreator provides methods to create files.
type FileCreator interface {
Create(ctx context.Context, f File) error
}
// FileUpdater provides methods to update files.
type FileUpdater interface {
Update(ctx context.Context, f File) error
}
// FileDestroyer provides methods to destroy files.
type FileDestroyer interface {
Destroy(ctx context.Context, id FileID) error
}
type FileFinderCreator interface {
FileFinder
FileCreator
}
type FileFinderUpdater interface {
FileFinder
FileUpdater
}
type FileFinderDestroyer interface {
FileFinder
FileDestroyer
}
// FileReader provides all methods to read files.
type FileReader interface {
FileFinder
FileQueryer
FileCounter
GetCaptions(ctx context.Context, fileID FileID) ([]*VideoCaption, error)
IsPrimary(ctx context.Context, fileID FileID) (bool, error)
}
// FileWriter provides all methods to modify files.
type FileWriter interface {
FileCreator
FileUpdater
FileDestroyer
UpdateCaptions(ctx context.Context, fileID FileID, captions []*VideoCaption) error
}
// FileReaderWriter provides all file methods.
type FileReaderWriter interface {
FileReader
FileWriter
}

View File

@@ -0,0 +1,64 @@
package models
import "context"
// FolderGetter provides methods to get folders by ID.
type FolderGetter interface {
Find(ctx context.Context, id FolderID) (*Folder, error)
}
// FolderFinder provides methods to find folders.
type FolderFinder interface {
FolderGetter
FindAllInPaths(ctx context.Context, p []string, limit, offset int) ([]*Folder, error)
FindByPath(ctx context.Context, path string) (*Folder, error)
FindByZipFileID(ctx context.Context, zipFileID FileID) ([]*Folder, error)
FindByParentFolderID(ctx context.Context, parentFolderID FolderID) ([]*Folder, error)
}
type FolderCounter interface {
CountAllInPaths(ctx context.Context, p []string) (int, error)
}
// FolderCreator provides methods to create folders.
type FolderCreator interface {
Create(ctx context.Context, f *Folder) error
}
// FolderUpdater provides methods to update folders.
type FolderUpdater interface {
Update(ctx context.Context, f *Folder) error
}
type FolderDestroyer interface {
Destroy(ctx context.Context, id FolderID) error
}
type FolderFinderCreator interface {
FolderFinder
FolderCreator
}
type FolderFinderDestroyer interface {
FolderFinder
FolderDestroyer
}
// FolderReader provides all methods to read folders.
type FolderReader interface {
FolderFinder
FolderCounter
}
// FolderWriter provides all methods to modify folders.
type FolderWriter interface {
FolderCreator
FolderUpdater
FolderDestroyer
}
// FolderReaderWriter provides all folder methods.
type FolderReaderWriter interface {
FolderReader
FolderWriter
}

View File

@@ -0,0 +1,91 @@
package models
import "context"
// GalleryGetter provides methods to get galleries by ID.
type GalleryGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Gallery, error)
Find(ctx context.Context, id int) (*Gallery, error)
}
// GalleryFinder provides methods to find galleries.
type GalleryFinder interface {
GalleryGetter
FindByFingerprints(ctx context.Context, fp []Fingerprint) ([]*Gallery, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Gallery, error)
FindByChecksums(ctx context.Context, checksums []string) ([]*Gallery, error)
FindByPath(ctx context.Context, path string) ([]*Gallery, error)
FindByFileID(ctx context.Context, fileID FileID) ([]*Gallery, error)
FindByFolderID(ctx context.Context, folderID FolderID) ([]*Gallery, error)
FindBySceneID(ctx context.Context, sceneID int) ([]*Gallery, error)
FindByImageID(ctx context.Context, imageID int) ([]*Gallery, error)
FindUserGalleryByTitle(ctx context.Context, title string) ([]*Gallery, error)
}
// GalleryQueryer provides methods to query galleries.
type GalleryQueryer interface {
Query(ctx context.Context, galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int, error)
QueryCount(ctx context.Context, galleryFilter *GalleryFilterType, findFilter *FindFilterType) (int, error)
}
// GalleryCounter provides methods to count galleries.
type GalleryCounter interface {
Count(ctx context.Context) (int, error)
CountByFileID(ctx context.Context, fileID FileID) (int, error)
}
// GalleryCreator provides methods to create galleries.
type GalleryCreator interface {
Create(ctx context.Context, newGallery *Gallery, fileIDs []FileID) error
}
// GalleryUpdater provides methods to update galleries.
type GalleryUpdater interface {
Update(ctx context.Context, updatedGallery *Gallery) error
UpdatePartial(ctx context.Context, id int, updatedGallery GalleryPartial) (*Gallery, error)
UpdateImages(ctx context.Context, galleryID int, imageIDs []int) error
}
// GalleryDestroyer provides methods to destroy galleries.
type GalleryDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type GalleryCreatorUpdater interface {
GalleryCreator
GalleryUpdater
}
// GalleryReader provides all methods to read galleries.
type GalleryReader interface {
GalleryFinder
GalleryQueryer
GalleryCounter
FileIDLoader
ImageIDLoader
SceneIDLoader
PerformerIDLoader
TagIDLoader
FileLoader
All(ctx context.Context) ([]*Gallery, error)
}
// GalleryWriter provides all methods to modify galleries.
type GalleryWriter interface {
GalleryCreator
GalleryUpdater
GalleryDestroyer
AddFileID(ctx context.Context, id int, fileID FileID) error
AddImages(ctx context.Context, galleryID int, imageIDs ...int) error
RemoveImages(ctx context.Context, galleryID int, imageIDs ...int) error
}
// GalleryReaderWriter provides all gallery methods.
type GalleryReaderWriter interface {
GalleryReader
GalleryWriter
}

View File

@@ -0,0 +1,55 @@
package models
import "context"
// GalleryChapterGetter provides methods to get gallery chapters by ID.
type GalleryChapterGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*GalleryChapter, error)
Find(ctx context.Context, id int) (*GalleryChapter, error)
}
// GalleryChapterFinder provides methods to find gallery chapters.
type GalleryChapterFinder interface {
GalleryChapterGetter
FindByGalleryID(ctx context.Context, galleryID int) ([]*GalleryChapter, error)
}
// GalleryChapterCreator provides methods to create gallery chapters.
type GalleryChapterCreator interface {
Create(ctx context.Context, newGalleryChapter *GalleryChapter) error
}
// GalleryChapterUpdater provides methods to update gallery chapters.
type GalleryChapterUpdater interface {
Update(ctx context.Context, updatedGalleryChapter *GalleryChapter) error
UpdatePartial(ctx context.Context, id int, updatedGalleryChapter GalleryChapterPartial) (*GalleryChapter, error)
}
// GalleryChapterDestroyer provides methods to destroy gallery chapters.
type GalleryChapterDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type GalleryChapterCreatorUpdater interface {
GalleryChapterCreator
GalleryChapterUpdater
}
// GalleryChapterReader provides all methods to read gallery chapters.
type GalleryChapterReader interface {
GalleryChapterFinder
}
// GalleryChapterWriter provides all methods to modify gallery chapters.
type GalleryChapterWriter interface {
GalleryChapterCreator
GalleryChapterUpdater
GalleryChapterDestroyer
}
// GalleryChapterReaderWriter provides all gallery chapter methods.
type GalleryChapterReaderWriter interface {
GalleryChapterReader
GalleryChapterWriter
}

View File

@@ -0,0 +1,92 @@
package models
import "context"
// ImageGetter provides methods to get images by ID.
type ImageGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Image, error)
Find(ctx context.Context, id int) (*Image, error)
}
// ImageFinder provides methods to find images.
type ImageFinder interface {
ImageGetter
FindByFingerprints(ctx context.Context, fp []Fingerprint) ([]*Image, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Image, error)
FindByFileID(ctx context.Context, fileID FileID) ([]*Image, error)
FindByFolderID(ctx context.Context, fileID FolderID) ([]*Image, error)
FindByZipFileID(ctx context.Context, zipFileID FileID) ([]*Image, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Image, error)
}
// ImageQueryer provides methods to query images.
type ImageQueryer interface {
Query(ctx context.Context, options ImageQueryOptions) (*ImageQueryResult, error)
QueryCount(ctx context.Context, imageFilter *ImageFilterType, findFilter *FindFilterType) (int, error)
}
// ImageCounter provides methods to count images.
type ImageCounter interface {
Count(ctx context.Context) (int, error)
CountByFileID(ctx context.Context, fileID FileID) (int, error)
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
}
// ImageCreator provides methods to create images.
type ImageCreator interface {
Create(ctx context.Context, newImage *ImageCreateInput) error
}
// ImageUpdater provides methods to update images.
type ImageUpdater interface {
Update(ctx context.Context, updatedImage *Image) error
UpdatePartial(ctx context.Context, id int, partial ImagePartial) (*Image, error)
UpdatePerformers(ctx context.Context, imageID int, performerIDs []int) error
UpdateTags(ctx context.Context, imageID int, tagIDs []int) error
}
// ImageDestroyer provides methods to destroy images.
type ImageDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type ImageCreatorUpdater interface {
ImageCreator
ImageUpdater
}
// ImageReader provides all methods to read images.
type ImageReader interface {
ImageFinder
ImageQueryer
ImageCounter
FileIDLoader
GalleryIDLoader
PerformerIDLoader
TagIDLoader
FileLoader
All(ctx context.Context) ([]*Image, error)
Size(ctx context.Context) (float64, error)
}
// ImageWriter provides all methods to modify images.
type ImageWriter interface {
ImageCreator
ImageUpdater
ImageDestroyer
AddFileID(ctx context.Context, id int, fileID FileID) error
IncrementOCounter(ctx context.Context, id int) (int, error)
DecrementOCounter(ctx context.Context, id int) (int, error)
ResetOCounter(ctx context.Context, id int) (int, error)
}
// ImageReaderWriter provides all image methods.
type ImageReaderWriter interface {
ImageReader
ImageWriter
}

View File

@@ -0,0 +1,86 @@
package models
import "context"
// MovieGetter provides methods to get movies by ID.
type MovieGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Movie, error)
Find(ctx context.Context, id int) (*Movie, error)
}
// MovieFinder provides methods to find movies.
type MovieFinder interface {
MovieGetter
FindByPerformerID(ctx context.Context, performerID int) ([]*Movie, error)
FindByStudioID(ctx context.Context, studioID int) ([]*Movie, error)
FindByName(ctx context.Context, name string, nocase bool) (*Movie, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Movie, error)
}
// MovieQueryer provides methods to query movies.
type MovieQueryer interface {
Query(ctx context.Context, movieFilter *MovieFilterType, findFilter *FindFilterType) ([]*Movie, int, error)
QueryCount(ctx context.Context, movieFilter *MovieFilterType, findFilter *FindFilterType) (int, error)
}
// MovieCounter provides methods to count movies.
type MovieCounter interface {
Count(ctx context.Context) (int, error)
CountByPerformerID(ctx context.Context, performerID int) (int, error)
CountByStudioID(ctx context.Context, studioID int) (int, error)
}
// MovieCreator provides methods to create movies.
type MovieCreator interface {
Create(ctx context.Context, newMovie *Movie) error
}
// MovieUpdater provides methods to update movies.
type MovieUpdater interface {
Update(ctx context.Context, updatedMovie *Movie) error
UpdatePartial(ctx context.Context, id int, updatedMovie MoviePartial) (*Movie, error)
UpdateFrontImage(ctx context.Context, movieID int, frontImage []byte) error
UpdateBackImage(ctx context.Context, movieID int, backImage []byte) error
}
// MovieDestroyer provides methods to destroy movies.
type MovieDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type MovieCreatorUpdater interface {
MovieCreator
MovieUpdater
}
type MovieFinderCreator interface {
MovieFinder
MovieCreator
}
// MovieReader provides all methods to read movies.
type MovieReader interface {
MovieFinder
MovieQueryer
MovieCounter
All(ctx context.Context) ([]*Movie, error)
GetFrontImage(ctx context.Context, movieID int) ([]byte, error)
HasFrontImage(ctx context.Context, movieID int) (bool, error)
GetBackImage(ctx context.Context, movieID int) ([]byte, error)
HasBackImage(ctx context.Context, movieID int) (bool, error)
}
// MovieWriter provides all methods to modify movies.
type MovieWriter interface {
MovieCreator
MovieUpdater
MovieDestroyer
}
// MovieReaderWriter provides all movie methods.
type MovieReaderWriter interface {
MovieReader
MovieWriter
}

View File

@@ -0,0 +1,98 @@
package models
import "context"
// PerformerGetter provides methods to get performers by ID.
type PerformerGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Performer, error)
Find(ctx context.Context, id int) (*Performer, error)
}
// PerformerFinder provides methods to find performers.
type PerformerFinder interface {
PerformerGetter
FindBySceneID(ctx context.Context, sceneID int) ([]*Performer, error)
FindByImageID(ctx context.Context, imageID int) ([]*Performer, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Performer, error)
FindByStashID(ctx context.Context, stashID StashID) ([]*Performer, error)
FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*Performer, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Performer, error)
}
// PerformerQueryer provides methods to query performers.
type PerformerQueryer interface {
Query(ctx context.Context, performerFilter *PerformerFilterType, findFilter *FindFilterType) ([]*Performer, int, error)
QueryCount(ctx context.Context, galleryFilter *PerformerFilterType, findFilter *FindFilterType) (int, error)
}
type PerformerAutoTagQueryer interface {
PerformerQueryer
AliasLoader
// TODO - this interface is temporary until the filter schema can fully
// support the query needed
QueryForAutoTag(ctx context.Context, words []string) ([]*Performer, error)
}
// PerformerCounter provides methods to count performers.
type PerformerCounter interface {
Count(ctx context.Context) (int, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
}
// PerformerCreator provides methods to create performers.
type PerformerCreator interface {
Create(ctx context.Context, newPerformer *Performer) error
}
// PerformerUpdater provides methods to update performers.
type PerformerUpdater interface {
Update(ctx context.Context, updatedPerformer *Performer) error
UpdatePartial(ctx context.Context, id int, updatedPerformer PerformerPartial) (*Performer, error)
UpdateImage(ctx context.Context, performerID int, image []byte) error
}
// PerformerDestroyer provides methods to destroy performers.
type PerformerDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type PerformerFinderCreator interface {
PerformerFinder
PerformerCreator
}
type PerformerCreatorUpdater interface {
PerformerCreator
PerformerUpdater
}
// PerformerReader provides all methods to read performers.
type PerformerReader interface {
PerformerFinder
PerformerQueryer
PerformerAutoTagQueryer
PerformerCounter
AliasLoader
StashIDLoader
TagIDLoader
All(ctx context.Context) ([]*Performer, error)
GetImage(ctx context.Context, performerID int) ([]byte, error)
HasImage(ctx context.Context, performerID int) (bool, error)
}
// PerformerWriter provides all methods to modify performers.
type PerformerWriter interface {
PerformerCreator
PerformerUpdater
PerformerDestroyer
}
// PerformerReaderWriter provides all performer methods.
type PerformerReaderWriter interface {
PerformerReader
PerformerWriter
}

View File

@@ -0,0 +1,115 @@
package models
import "context"
// SceneGetter provides methods to get scenes by ID.
type SceneGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Scene, error)
Find(ctx context.Context, id int) (*Scene, error)
}
// SceneFinder provides methods to find scenes.
type SceneFinder interface {
SceneGetter
FindByFingerprints(ctx context.Context, fp []Fingerprint) ([]*Scene, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Scene, error)
FindByOSHash(ctx context.Context, oshash string) ([]*Scene, error)
FindByPath(ctx context.Context, path string) ([]*Scene, error)
FindByFileID(ctx context.Context, fileID FileID) ([]*Scene, error)
FindByPrimaryFileID(ctx context.Context, fileID FileID) ([]*Scene, error)
FindByPerformerID(ctx context.Context, performerID int) ([]*Scene, error)
FindByGalleryID(ctx context.Context, performerID int) ([]*Scene, error)
FindByMovieID(ctx context.Context, movieID int) ([]*Scene, error)
FindDuplicates(ctx context.Context, distance int, durationDiff float64) ([][]*Scene, error)
}
// SceneQueryer provides methods to query scenes.
type SceneQueryer interface {
Query(ctx context.Context, options SceneQueryOptions) (*SceneQueryResult, error)
QueryCount(ctx context.Context, sceneFilter *SceneFilterType, findFilter *FindFilterType) (int, error)
}
// SceneCounter provides methods to count scenes.
type SceneCounter interface {
Count(ctx context.Context) (int, error)
CountByPerformerID(ctx context.Context, performerID int) (int, error)
CountByMovieID(ctx context.Context, movieID int) (int, error)
CountByFileID(ctx context.Context, fileID FileID) (int, error)
CountByStudioID(ctx context.Context, studioID int) (int, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
CountMissingChecksum(ctx context.Context) (int, error)
CountMissingOSHash(ctx context.Context) (int, error)
OCount(ctx context.Context) (int, error)
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
PlayCount(ctx context.Context) (int, error)
UniqueScenePlayCount(ctx context.Context) (int, error)
}
// SceneCreator provides methods to create scenes.
type SceneCreator interface {
Create(ctx context.Context, newScene *Scene, fileIDs []FileID) error
}
// SceneUpdater provides methods to update scenes.
type SceneUpdater interface {
Update(ctx context.Context, updatedScene *Scene) error
UpdatePartial(ctx context.Context, id int, updatedScene ScenePartial) (*Scene, error)
UpdateCover(ctx context.Context, sceneID int, cover []byte) error
}
// SceneDestroyer provides methods to destroy scenes.
type SceneDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type SceneCreatorUpdater interface {
SceneCreator
SceneUpdater
}
// SceneReader provides all methods to read scenes.
type SceneReader interface {
SceneFinder
SceneQueryer
SceneCounter
URLLoader
FileIDLoader
GalleryIDLoader
PerformerIDLoader
TagIDLoader
SceneMovieLoader
StashIDLoader
VideoFileLoader
All(ctx context.Context) ([]*Scene, error)
Wall(ctx context.Context, q *string) ([]*Scene, error)
Size(ctx context.Context) (float64, error)
Duration(ctx context.Context) (float64, error)
PlayDuration(ctx context.Context) (float64, error)
GetCover(ctx context.Context, sceneID int) ([]byte, error)
HasCover(ctx context.Context, sceneID int) (bool, error)
}
// SceneWriter provides all methods to modify scenes.
type SceneWriter interface {
SceneCreator
SceneUpdater
SceneDestroyer
AddFileID(ctx context.Context, id int, fileID FileID) error
AddGalleryIDs(ctx context.Context, sceneID int, galleryIDs []int) error
AssignFiles(ctx context.Context, sceneID int, fileID []FileID) error
IncrementOCounter(ctx context.Context, id int) (int, error)
DecrementOCounter(ctx context.Context, id int) (int, error)
ResetOCounter(ctx context.Context, id int) (int, error)
SaveActivity(ctx context.Context, sceneID int, resumeTime *float64, playDuration *float64) (bool, error)
IncrementWatchCount(ctx context.Context, sceneID int) (int, error)
}
// SceneReaderWriter provides all scene methods.
type SceneReaderWriter interface {
SceneReader
SceneWriter
}

View File

@@ -0,0 +1,76 @@
package models
import "context"
// SceneMarkerGetter provides methods to get scene markers by ID.
type SceneMarkerGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*SceneMarker, error)
Find(ctx context.Context, id int) (*SceneMarker, error)
}
// SceneMarkerFinder provides methods to find scene markers.
type SceneMarkerFinder interface {
SceneMarkerGetter
FindBySceneID(ctx context.Context, sceneID int) ([]*SceneMarker, error)
}
// SceneMarkerQueryer provides methods to query scene markers.
type SceneMarkerQueryer interface {
Query(ctx context.Context, sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) ([]*SceneMarker, int, error)
QueryCount(ctx context.Context, sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) (int, error)
}
// SceneMarkerCounter provides methods to count scene markers.
type SceneMarkerCounter interface {
Count(ctx context.Context) (int, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
}
// SceneMarkerCreator provides methods to create scene markers.
type SceneMarkerCreator interface {
Create(ctx context.Context, newSceneMarker *SceneMarker) error
}
// SceneMarkerUpdater provides methods to update scene markers.
type SceneMarkerUpdater interface {
Update(ctx context.Context, updatedSceneMarker *SceneMarker) error
UpdatePartial(ctx context.Context, id int, updatedSceneMarker SceneMarkerPartial) (*SceneMarker, error)
UpdateTags(ctx context.Context, markerID int, tagIDs []int) error
}
// SceneMarkerDestroyer provides methods to destroy scene markers.
type SceneMarkerDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type SceneMarkerCreatorUpdater interface {
SceneMarkerCreator
SceneMarkerUpdater
}
// SceneMarkerReader provides all methods to read scene markers.
type SceneMarkerReader interface {
SceneMarkerFinder
SceneMarkerQueryer
SceneMarkerCounter
TagIDLoader
All(ctx context.Context) ([]*SceneMarker, error)
Wall(ctx context.Context, q *string) ([]*SceneMarker, error)
GetMarkerStrings(ctx context.Context, q *string, sort *string) ([]*MarkerStringsResultType, error)
}
// SceneMarkerWriter provides all methods to modify scene markers.
type SceneMarkerWriter interface {
SceneMarkerCreator
SceneMarkerUpdater
SceneMarkerDestroyer
}
// SceneMarkerReaderWriter provides all scene marker methods.
type SceneMarkerReaderWriter interface {
SceneMarkerReader
SceneMarkerWriter
}

View File

@@ -0,0 +1,94 @@
package models
import "context"
// StudioGetter provides methods to get studios by ID.
type StudioGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Studio, error)
Find(ctx context.Context, id int) (*Studio, error)
}
// StudioFinder provides methods to find studios.
type StudioFinder interface {
StudioGetter
FindChildren(ctx context.Context, id int) ([]*Studio, error)
FindBySceneID(ctx context.Context, sceneID int) (*Studio, error)
FindByStashID(ctx context.Context, stashID StashID) ([]*Studio, error)
FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*Studio, error)
FindByName(ctx context.Context, name string, nocase bool) (*Studio, error)
}
// StudioQueryer provides methods to query studios.
type StudioQueryer interface {
Query(ctx context.Context, studioFilter *StudioFilterType, findFilter *FindFilterType) ([]*Studio, int, error)
}
type StudioAutoTagQueryer interface {
StudioQueryer
AliasLoader
// TODO - this interface is temporary until the filter schema can fully
// support the query needed
QueryForAutoTag(ctx context.Context, words []string) ([]*Studio, error)
}
// StudioCounter provides methods to count studios.
type StudioCounter interface {
Count(ctx context.Context) (int, error)
}
// StudioCreator provides methods to create studios.
type StudioCreator interface {
Create(ctx context.Context, newStudio *Studio) error
}
// StudioUpdater provides methods to update studios.
type StudioUpdater interface {
Update(ctx context.Context, updatedStudio *Studio) error
UpdatePartial(ctx context.Context, updatedStudio StudioPartial) (*Studio, error)
UpdateImage(ctx context.Context, studioID int, image []byte) error
}
// StudioDestroyer provides methods to destroy studios.
type StudioDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type StudioFinderCreator interface {
StudioFinder
StudioCreator
}
type StudioCreatorUpdater interface {
StudioCreator
StudioUpdater
}
// StudioReader provides all methods to read studios.
type StudioReader interface {
StudioFinder
StudioQueryer
StudioAutoTagQueryer
StudioCounter
AliasLoader
StashIDLoader
All(ctx context.Context) ([]*Studio, error)
GetImage(ctx context.Context, studioID int) ([]byte, error)
HasImage(ctx context.Context, studioID int) (bool, error)
}
// StudioWriter provides all methods to modify studios.
type StudioWriter interface {
StudioCreator
StudioUpdater
StudioDestroyer
}
// StudioReaderWriter provides all studio methods.
type StudioReaderWriter interface {
StudioReader
StudioWriter
}

View File

@@ -0,0 +1,104 @@
package models
import "context"
// TagGetter provides methods to get tags by ID.
type TagGetter interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Tag, error)
Find(ctx context.Context, id int) (*Tag, error)
}
// TagFinder provides methods to find tags.
type TagFinder interface {
TagGetter
FindAllAncestors(ctx context.Context, tagID int, excludeIDs []int) ([]*TagPath, error)
FindAllDescendants(ctx context.Context, tagID int, excludeIDs []int) ([]*TagPath, error)
FindByParentTagID(ctx context.Context, parentID int) ([]*Tag, error)
FindByChildTagID(ctx context.Context, childID int) ([]*Tag, error)
FindBySceneID(ctx context.Context, sceneID int) ([]*Tag, error)
FindByImageID(ctx context.Context, imageID int) ([]*Tag, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Tag, error)
FindByPerformerID(ctx context.Context, performerID int) ([]*Tag, error)
FindBySceneMarkerID(ctx context.Context, sceneMarkerID int) ([]*Tag, error)
FindByName(ctx context.Context, name string, nocase bool) (*Tag, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Tag, error)
}
// TagQueryer provides methods to query tags.
type TagQueryer interface {
Query(ctx context.Context, tagFilter *TagFilterType, findFilter *FindFilterType) ([]*Tag, int, error)
}
type TagAutoTagQueryer interface {
TagQueryer
AliasLoader
// TODO - this interface is temporary until the filter schema can fully
// support the query needed
QueryForAutoTag(ctx context.Context, words []string) ([]*Tag, error)
}
// TagCounter provides methods to count tags.
type TagCounter interface {
Count(ctx context.Context) (int, error)
}
// TagCreator provides methods to create tags.
type TagCreator interface {
Create(ctx context.Context, newTag *Tag) error
}
// TagUpdater provides methods to update tags.
type TagUpdater interface {
Update(ctx context.Context, updatedTag *Tag) error
UpdatePartial(ctx context.Context, id int, updateTag TagPartial) (*Tag, error)
UpdateAliases(ctx context.Context, tagID int, aliases []string) error
UpdateImage(ctx context.Context, tagID int, image []byte) error
UpdateParentTags(ctx context.Context, tagID int, parentIDs []int) error
UpdateChildTags(ctx context.Context, tagID int, parentIDs []int) error
}
// TagDestroyer provides methods to destroy tags.
type TagDestroyer interface {
Destroy(ctx context.Context, id int) error
}
type TagFinderCreator interface {
TagFinder
TagCreator
}
type TagCreatorUpdater interface {
TagCreator
TagUpdater
}
// TagReader provides all methods to read tags.
type TagReader interface {
TagFinder
TagQueryer
TagAutoTagQueryer
TagCounter
AliasLoader
All(ctx context.Context) ([]*Tag, error)
GetImage(ctx context.Context, tagID int) ([]byte, error)
HasImage(ctx context.Context, tagID int) (bool, error)
}
// TagWriter provides all methods to modify tags.
type TagWriter interface {
TagCreator
TagUpdater
TagDestroyer
Merge(ctx context.Context, source []int, destination int) error
}
// TagReaderWriter provides all tags methods.
type TagReaderWriter interface {
TagReader
TagWriter
}

View File

@@ -1,10 +1,6 @@
package models
import (
"context"
"github.com/stashapp/stash/pkg/file"
)
import "context"
type PHashDuplicationCriterionInput struct {
Duplicated *bool `json:"duplicated"`
@@ -112,7 +108,7 @@ type SceneQueryResult struct {
TotalDuration float64
TotalSize float64
finder SceneFinder
getter SceneGetter
scenes []*Scene
resolveErr error
}
@@ -129,83 +125,16 @@ type ScenesDestroyInput struct {
DeleteGenerated *bool `json:"delete_generated"`
}
func NewSceneQueryResult(finder SceneFinder) *SceneQueryResult {
func NewSceneQueryResult(getter SceneGetter) *SceneQueryResult {
return &SceneQueryResult{
finder: finder,
getter: getter,
}
}
func (r *SceneQueryResult) Resolve(ctx context.Context) ([]*Scene, error) {
// cache results
if r.scenes == nil && r.resolveErr == nil {
r.scenes, r.resolveErr = r.finder.FindMany(ctx, r.IDs)
r.scenes, r.resolveErr = r.getter.FindMany(ctx, r.IDs)
}
return r.scenes, r.resolveErr
}
type SceneFinder interface {
// TODO - rename this to Find and remove existing method
FindMany(ctx context.Context, ids []int) ([]*Scene, error)
}
type SceneReader interface {
SceneFinder
// TODO - remove this in another PR
Find(ctx context.Context, id int) (*Scene, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Scene, error)
FindByOSHash(ctx context.Context, oshash string) ([]*Scene, error)
FindByPath(ctx context.Context, path string) ([]*Scene, error)
FindByPerformerID(ctx context.Context, performerID int) ([]*Scene, error)
FindByGalleryID(ctx context.Context, performerID int) ([]*Scene, error)
FindDuplicates(ctx context.Context, distance int, durationDiff float64) ([][]*Scene, error)
URLLoader
GalleryIDLoader
PerformerIDLoader
TagIDLoader
SceneMovieLoader
StashIDLoader
VideoFileLoader
CountByPerformerID(ctx context.Context, performerID int) (int, error)
OCountByPerformerID(ctx context.Context, performerID int) (int, error)
OCount(ctx context.Context) (int, error)
// FindByStudioID(studioID int) ([]*Scene, error)
FindByMovieID(ctx context.Context, movieID int) ([]*Scene, error)
CountByMovieID(ctx context.Context, movieID int) (int, error)
Count(ctx context.Context) (int, error)
PlayCount(ctx context.Context) (int, error)
UniqueScenePlayCount(ctx context.Context) (int, error)
Size(ctx context.Context) (float64, error)
Duration(ctx context.Context) (float64, error)
PlayDuration(ctx context.Context) (float64, error)
// SizeCount() (string, error)
CountByStudioID(ctx context.Context, studioID int) (int, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
CountMissingChecksum(ctx context.Context) (int, error)
CountMissingOSHash(ctx context.Context) (int, error)
Wall(ctx context.Context, q *string) ([]*Scene, error)
All(ctx context.Context) ([]*Scene, error)
Query(ctx context.Context, options SceneQueryOptions) (*SceneQueryResult, error)
QueryCount(ctx context.Context, sceneFilter *SceneFilterType, findFilter *FindFilterType) (int, error)
GetCover(ctx context.Context, sceneID int) ([]byte, error)
HasCover(ctx context.Context, sceneID int) (bool, error)
}
type SceneWriter interface {
Create(ctx context.Context, newScene *Scene, fileIDs []file.ID) error
Update(ctx context.Context, updatedScene *Scene) error
UpdatePartial(ctx context.Context, id int, updatedScene ScenePartial) (*Scene, error)
IncrementOCounter(ctx context.Context, id int) (int, error)
DecrementOCounter(ctx context.Context, id int) (int, error)
ResetOCounter(ctx context.Context, id int) (int, error)
SaveActivity(ctx context.Context, id int, resumeTime *float64, playDuration *float64) (bool, error)
IncrementWatchCount(ctx context.Context, id int) (int, error)
Destroy(ctx context.Context, id int) error
UpdateCover(ctx context.Context, sceneID int, cover []byte) error
}
type SceneReaderWriter interface {
SceneReader
SceneWriter
}

View File

@@ -1,7 +1,5 @@
package models
import "context"
type SceneMarkerFilterType struct {
// Filter to only include scene markers with this tag
TagID *string `json:"tag_id"`
@@ -28,30 +26,3 @@ type MarkerStringsResultType struct {
ID string `json:"id"`
Title string `json:"title"`
}
type SceneMarkerReader interface {
Find(ctx context.Context, id int) (*SceneMarker, error)
FindMany(ctx context.Context, ids []int) ([]*SceneMarker, error)
FindBySceneID(ctx context.Context, sceneID int) ([]*SceneMarker, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
GetMarkerStrings(ctx context.Context, q *string, sort *string) ([]*MarkerStringsResultType, error)
Wall(ctx context.Context, q *string) ([]*SceneMarker, error)
Count(ctx context.Context) (int, error)
All(ctx context.Context) ([]*SceneMarker, error)
Query(ctx context.Context, sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) ([]*SceneMarker, int, error)
QueryCount(ctx context.Context, sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) (int, error)
GetTagIDs(ctx context.Context, imageID int) ([]int, error)
}
type SceneMarkerWriter interface {
Create(ctx context.Context, newSceneMarker *SceneMarker) error
Update(ctx context.Context, updatedSceneMarker *SceneMarker) error
UpdatePartial(ctx context.Context, id int, updatedSceneMarker SceneMarkerPartial) (*SceneMarker, error)
Destroy(ctx context.Context, id int) error
UpdateTags(ctx context.Context, markerID int, tagIDs []int) error
}
type SceneMarkerReaderWriter interface {
SceneMarkerReader
SceneMarkerWriter
}

View File

@@ -1,7 +1,5 @@
package models
import "context"
type StudioFilterType struct {
And *StudioFilterType `json:"AND"`
Or *StudioFilterType `json:"OR"`
@@ -37,39 +35,3 @@ type StudioFilterType struct {
// Filter by updated at
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
}
type StudioFinder interface {
FindMany(ctx context.Context, ids []int) ([]*Studio, error)
}
type StudioReader interface {
Find(ctx context.Context, id int) (*Studio, error)
StudioFinder
FindChildren(ctx context.Context, id int) ([]*Studio, error)
FindByName(ctx context.Context, name string, nocase bool) (*Studio, error)
FindByStashID(ctx context.Context, stashID StashID) ([]*Studio, error)
FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*Studio, error)
Count(ctx context.Context) (int, error)
All(ctx context.Context) ([]*Studio, error)
// TODO - this interface is temporary until the filter schema can fully
// support the query needed
QueryForAutoTag(ctx context.Context, words []string) ([]*Studio, error)
Query(ctx context.Context, studioFilter *StudioFilterType, findFilter *FindFilterType) ([]*Studio, int, error)
GetImage(ctx context.Context, studioID int) ([]byte, error)
HasImage(ctx context.Context, studioID int) (bool, error)
AliasLoader
StashIDLoader
}
type StudioWriter interface {
Create(ctx context.Context, newStudio *Studio) error
UpdatePartial(ctx context.Context, input StudioPartial) (*Studio, error)
Update(ctx context.Context, updatedStudio *Studio) error
Destroy(ctx context.Context, id int) error
UpdateImage(ctx context.Context, studioID int, image []byte) error
}
type StudioReaderWriter interface {
StudioReader
StudioWriter
}

View File

@@ -1,7 +1,5 @@
package models
import "context"
type TagFilterType struct {
And *TagFilterType `json:"AND"`
Or *TagFilterType `json:"OR"`
@@ -39,49 +37,3 @@ type TagFilterType struct {
// Filter by updated at
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
}
type TagFinder interface {
FindMany(ctx context.Context, ids []int) ([]*Tag, error)
}
type TagReader interface {
Find(ctx context.Context, id int) (*Tag, error)
TagFinder
FindBySceneID(ctx context.Context, sceneID int) ([]*Tag, error)
FindByPerformerID(ctx context.Context, performerID int) ([]*Tag, error)
FindBySceneMarkerID(ctx context.Context, sceneMarkerID int) ([]*Tag, error)
FindByImageID(ctx context.Context, imageID int) ([]*Tag, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Tag, error)
FindByName(ctx context.Context, name string, nocase bool) (*Tag, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Tag, error)
FindByParentTagID(ctx context.Context, parentID int) ([]*Tag, error)
FindByChildTagID(ctx context.Context, childID int) ([]*Tag, error)
Count(ctx context.Context) (int, error)
All(ctx context.Context) ([]*Tag, error)
// TODO - this interface is temporary until the filter schema can fully
// support the query needed
QueryForAutoTag(ctx context.Context, words []string) ([]*Tag, error)
Query(ctx context.Context, tagFilter *TagFilterType, findFilter *FindFilterType) ([]*Tag, int, error)
GetImage(ctx context.Context, tagID int) ([]byte, error)
HasImage(ctx context.Context, tagID int) (bool, error)
GetAliases(ctx context.Context, tagID int) ([]string, error)
FindAllAncestors(ctx context.Context, tagID int, excludeIDs []int) ([]*TagPath, error)
FindAllDescendants(ctx context.Context, tagID int, excludeIDs []int) ([]*TagPath, error)
}
type TagWriter interface {
Create(ctx context.Context, newTag *Tag) error
UpdatePartial(ctx context.Context, id int, updateTag TagPartial) (*Tag, error)
Update(ctx context.Context, updatedTag *Tag) error
Destroy(ctx context.Context, id int) error
UpdateImage(ctx context.Context, tagID int, image []byte) error
UpdateAliases(ctx context.Context, tagID int, aliases []string) error
Merge(ctx context.Context, source []int, destination int) error
UpdateParentTags(ctx context.Context, tagID int, parentIDs []int) error
UpdateChildTags(ctx context.Context, tagID int, parentIDs []int) error
}
type TagReaderWriter interface {
TagReader
TagWriter
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/utils"
)
@@ -18,7 +17,7 @@ type ImageGetter interface {
}
// ToJSON converts a Movie into its JSON equivalent.
func ToJSON(ctx context.Context, reader ImageGetter, studioReader studio.Finder, movie *models.Movie) (*jsonschema.Movie, error) {
func ToJSON(ctx context.Context, reader ImageGetter, studioReader models.StudioGetter, movie *models.Movie) (*jsonschema.Movie, error) {
newMovieJSON := jsonschema.Movie{
Name: movie.Name,
Aliases: movie.Aliases,

View File

@@ -6,24 +6,17 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/utils"
)
type ImageUpdater interface {
UpdateFrontImage(ctx context.Context, movieID int, frontImage []byte) error
UpdateBackImage(ctx context.Context, movieID int, backImage []byte) error
}
type NameFinderCreatorUpdater interface {
NameFinderCreator
Update(ctx context.Context, updatedMovie *models.Movie) error
ImageUpdater
type ImporterReaderWriter interface {
models.MovieCreatorUpdater
FindByName(ctx context.Context, name string, nocase bool) (*models.Movie, error)
}
type Importer struct {
ReaderWriter NameFinderCreatorUpdater
StudioWriter studio.NameFinderCreator
ReaderWriter ImporterReaderWriter
StudioWriter models.StudioFinderCreator
Input jsonschema.Movie
MissingRefBehaviour models.ImportMissingRefEnum

View File

@@ -7,15 +7,7 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type Queryer interface {
Query(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) ([]*models.Movie, int, error)
}
type CountQueryer interface {
QueryCount(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) (int, error)
}
func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByStudioID(ctx context.Context, r models.MovieQueryer, id int, depth *int) (int, error) {
filter := &models.MovieFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},

View File

@@ -1,12 +0,0 @@
package movie
import (
"context"
"github.com/stashapp/stash/pkg/models"
)
type NameFinderCreator interface {
FindByName(ctx context.Context, name string, nocase bool) (*models.Movie, error)
Create(ctx context.Context, newMovie *models.Movie) error
}

View File

@@ -10,19 +10,17 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/tag"
"github.com/stashapp/stash/pkg/utils"
)
type NameFinderCreatorUpdater interface {
NameFinderCreator
Update(ctx context.Context, updatedPerformer *models.Performer) error
UpdateImage(ctx context.Context, performerID int, image []byte) error
type ImporterReaderWriter interface {
models.PerformerCreatorUpdater
models.PerformerQueryer
}
type Importer struct {
ReaderWriter NameFinderCreatorUpdater
TagWriter tag.NameFinderCreator
ReaderWriter ImporterReaderWriter
TagWriter models.TagFinderCreator
Input jsonschema.Performer
MissingRefBehaviour models.ImportMissingRefEnum
@@ -65,7 +63,7 @@ func (i *Importer) populateTags(ctx context.Context) error {
return nil
}
func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
tags, err := tagWriter.FindByNames(ctx, names, false)
if err != nil {
return nil, err
@@ -100,7 +98,7 @@ func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []st
return tags, nil
}
func createTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string) ([]*models.Tag, error) {
func createTags(ctx context.Context, tagWriter models.TagFinderCreator, names []string) ([]*models.Tag, error) {
var ret []*models.Tag
for _, name := range names {
newTag := models.NewTag(name)

View File

@@ -7,15 +7,7 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type Queryer interface {
Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error)
}
type CountQueryer interface {
QueryCount(ctx context.Context, galleryFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (int, error)
}
func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByStudioID(ctx context.Context, r models.PerformerQueryer, id int, depth *int) (int, error) {
filter := &models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -27,7 +19,7 @@ func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (i
return r.QueryCount(ctx, filter, nil)
}
func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByTagID(ctx context.Context, r models.PerformerQueryer, id int, depth *int) (int, error) {
filter := &models.PerformerFilterType{
Tags: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@@ -39,7 +31,7 @@ func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int,
return r.QueryCount(ctx, filter, nil)
}
func CountByAppearsWith(ctx context.Context, r CountQueryer, id int) (int, error) {
func CountByAppearsWith(ctx context.Context, r models.PerformerQueryer, id int) (int, error) {
filter := &models.PerformerFilterType{
Performers: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(id)},

View File

@@ -1,13 +0,0 @@
package performer
import (
"context"
"github.com/stashapp/stash/pkg/models"
)
type NameFinderCreator interface {
FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error)
Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error)
Create(ctx context.Context, newPerformer *models.Performer) error
}

View File

@@ -6,12 +6,11 @@ import (
"fmt"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
)
func (s *Service) Create(ctx context.Context, input *models.Scene, fileIDs []file.ID, coverImage []byte) (*models.Scene, error) {
func (s *Service) Create(ctx context.Context, input *models.Scene, fileIDs []models.FileID, coverImage []byte) (*models.Scene, error) {
// title must be set if no files are provided
if input.Title == "" && len(fileIDs) == 0 {
return nil, errors.New("title must be set if scene has no files")

View File

@@ -105,15 +105,6 @@ func (d *FileDeleter) MarkMarkerFiles(scene *models.Scene, seconds int) error {
return d.Files(files)
}
type Destroyer interface {
Destroy(ctx context.Context, id int) error
}
type MarkerDestroyer interface {
FindBySceneID(ctx context.Context, sceneID int) ([]*models.SceneMarker, error)
Destroy(ctx context.Context, id int) error
}
// Destroy deletes a scene and its associated relationships from the
// database.
func (s *Service) Destroy(ctx context.Context, scene *models.Scene, fileDeleter *FileDeleter, deleteGenerated, deleteFile bool) error {
@@ -190,7 +181,7 @@ func (s *Service) deleteFiles(ctx context.Context, scene *models.Scene, fileDele
// DestroyMarker deletes the scene marker from the database and returns a
// function that removes the generated files, to be executed after the
// transaction is successfully committed.
func DestroyMarker(ctx context.Context, scene *models.Scene, sceneMarker *models.SceneMarker, qb MarkerDestroyer, fileDeleter *FileDeleter) error {
func DestroyMarker(ctx context.Context, scene *models.Scene, sceneMarker *models.SceneMarker, qb models.SceneMarkerDestroyer, fileDeleter *FileDeleter) error {
if err := qb.Destroy(ctx, sceneMarker.ID); err != nil {
return err
}

View File

@@ -11,8 +11,6 @@ import (
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
"github.com/stashapp/stash/pkg/utils"
)
@@ -20,18 +18,10 @@ type CoverGetter interface {
GetCover(ctx context.Context, sceneID int) ([]byte, error)
}
type MarkerTagFinder interface {
tag.Finder
TagFinder
FindBySceneMarkerID(ctx context.Context, sceneMarkerID int) ([]*models.Tag, error)
}
type MarkerFinder interface {
FindBySceneID(ctx context.Context, sceneID int) ([]*models.SceneMarker, error)
}
type TagFinder interface {
models.TagGetter
FindBySceneID(ctx context.Context, sceneID int) ([]*models.Tag, error)
FindBySceneMarkerID(ctx context.Context, sceneMarkerID int) ([]*models.Tag, error)
}
// ToBasicJSON converts a scene object into its JSON object equivalent. It
@@ -88,7 +78,7 @@ func ToBasicJSON(ctx context.Context, reader CoverGetter, scene *models.Scene) (
// GetStudioName returns the name of the provided scene's studio. It returns an
// empty string if there is no studio assigned to the scene.
func GetStudioName(ctx context.Context, reader studio.Finder, scene *models.Scene) (string, error) {
func GetStudioName(ctx context.Context, reader models.StudioGetter, scene *models.Scene) (string, error) {
if scene.StudioID != nil {
studio, err := reader.Find(ctx, *scene.StudioID)
if err != nil {
@@ -126,7 +116,7 @@ func getTagNames(tags []*models.Tag) []string {
}
// GetDependentTagIDs returns a slice of unique tag IDs that this scene references.
func GetDependentTagIDs(ctx context.Context, tags MarkerTagFinder, markerReader MarkerFinder, scene *models.Scene) ([]int, error) {
func GetDependentTagIDs(ctx context.Context, tags TagFinder, markerReader models.SceneMarkerFinder, scene *models.Scene) ([]int, error) {
var ret []int
t, err := tags.FindBySceneID(ctx, scene.ID)
@@ -158,13 +148,9 @@ func GetDependentTagIDs(ctx context.Context, tags MarkerTagFinder, markerReader
return ret, nil
}
type MovieFinder interface {
Find(ctx context.Context, id int) (*models.Movie, error)
}
// GetSceneMoviesJSON returns a slice of SceneMovie JSON representation objects
// corresponding to the provided scene's scene movie relationships.
func GetSceneMoviesJSON(ctx context.Context, movieReader MovieFinder, scene *models.Scene) ([]jsonschema.SceneMovie, error) {
func GetSceneMoviesJSON(ctx context.Context, movieReader models.MovieGetter, scene *models.Scene) ([]jsonschema.SceneMovie, error) {
sceneMovies := scene.Movies.List()
var results []jsonschema.SceneMovie
@@ -202,7 +188,7 @@ func GetDependentMovieIDs(ctx context.Context, scene *models.Scene) ([]int, erro
// GetSceneMarkersJSON returns a slice of SceneMarker JSON representation
// objects corresponding to the provided scene's markers.
func GetSceneMarkersJSON(ctx context.Context, markerReader MarkerFinder, tagReader MarkerTagFinder, scene *models.Scene) ([]jsonschema.SceneMarker, error) {
func GetSceneMarkersJSON(ctx context.Context, markerReader models.SceneMarkerFinder, tagReader TagFinder, scene *models.Scene) ([]jsonschema.SceneMarker, error) {
sceneMarkers, err := markerReader.FindBySceneID(ctx, scene.ID)
if err != nil {
return nil, fmt.Errorf("error getting scene markers: %v", err)

View File

@@ -3,7 +3,6 @@ package scene
import (
"errors"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@@ -93,9 +92,9 @@ func createFullScene(id int) models.Scene {
Rating: &rating,
Organized: organized,
URLs: models.NewRelatedStrings([]string{url}),
Files: models.NewRelatedVideoFiles([]*file.VideoFile{
Files: models.NewRelatedVideoFiles([]*models.VideoFile{
{
BaseFile: &file.BaseFile{
BaseFile: &models.BaseFile{
Path: path,
},
},
@@ -111,9 +110,9 @@ func createFullScene(id int) models.Scene {
func createEmptyScene(id int) models.Scene {
return models.Scene{
ID: id,
Files: models.NewRelatedVideoFiles([]*file.VideoFile{
Files: models.NewRelatedVideoFiles([]*models.VideoFile{
{
BaseFile: &file.BaseFile{
BaseFile: &models.BaseFile{
Path: path,
},
},

View File

@@ -450,11 +450,11 @@ func (p *FilenameParser) initWhiteSpaceRegex() {
}
type FilenameParserRepository struct {
Scene Queryer
Scene models.SceneQueryer
Performer PerformerNamesFinder
Studio studio.Queryer
Studio models.StudioQueryer
Movie MovieNameFinder
Tag tag.Queryer
Tag models.TagQueryer
}
func (p *FilenameParser) Parse(ctx context.Context, repo FilenameParserRepository) ([]*models.SceneParserResult, int, error) {
@@ -544,7 +544,7 @@ func (p *FilenameParser) queryPerformer(ctx context.Context, qb PerformerNamesFi
return ret
}
func (p *FilenameParser) queryStudio(ctx context.Context, qb studio.Queryer, studioName string) *models.Studio {
func (p *FilenameParser) queryStudio(ctx context.Context, qb models.StudioQueryer, studioName string) *models.Studio {
// massage the performer name
studioName = delimiterRE.ReplaceAllString(studioName, " ")
@@ -587,7 +587,7 @@ func (p *FilenameParser) queryMovie(ctx context.Context, qb MovieNameFinder, mov
return ret
}
func (p *FilenameParser) queryTag(ctx context.Context, qb tag.Queryer, tagName string) *models.Tag {
func (p *FilenameParser) queryTag(ctx context.Context, qb models.TagQueryer, tagName string) *models.Tag {
// massage the tag name
tagName = delimiterRE.ReplaceAllString(tagName, " ")
@@ -626,7 +626,7 @@ func (p *FilenameParser) setPerformers(ctx context.Context, qb PerformerNamesFin
}
}
func (p *FilenameParser) setTags(ctx context.Context, qb tag.Queryer, h sceneHolder, result *models.SceneParserResult) {
func (p *FilenameParser) setTags(ctx context.Context, qb models.TagQueryer, h sceneHolder, result *models.SceneParserResult) {
// query for each performer
tagsSet := make(map[int]bool)
for _, tagName := range h.tags {
@@ -642,7 +642,7 @@ func (p *FilenameParser) setTags(ctx context.Context, qb tag.Queryer, h sceneHol
}
}
func (p *FilenameParser) setStudio(ctx context.Context, qb studio.Queryer, h sceneHolder, result *models.SceneParserResult) {
func (p *FilenameParser) setStudio(ctx context.Context, qb models.StudioQueryer, h sceneHolder, result *models.SceneParserResult) {
// query for each performer
if h.studio != "" {
studio := p.queryStudio(ctx, qb, h.studio)

View File

@@ -1,18 +1,17 @@
package scene
import (
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// GetHash returns the hash of the file, based on the hash algorithm provided. If
// hash algorithm is MD5, then Checksum is returned. Otherwise, OSHash is returned.
func GetHash(f file.File, hashAlgorithm models.HashAlgorithm) string {
func GetHash(f models.File, hashAlgorithm models.HashAlgorithm) string {
switch hashAlgorithm {
case models.HashAlgorithmMd5:
return f.Base().Fingerprints.GetString(file.FingerprintTypeMD5)
return f.Base().Fingerprints.GetString(models.FingerprintTypeMD5)
case models.HashAlgorithmOshash:
return f.Base().Fingerprints.GetString(file.FingerprintTypeOshash)
return f.Base().Fingerprints.GetString(models.FingerprintTypeOshash)
default:
panic("unknown hash algorithm")
}

View File

@@ -5,32 +5,25 @@ import (
"fmt"
"strings"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/movie"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
"github.com/stashapp/stash/pkg/utils"
)
type FullCreatorUpdater interface {
CreatorUpdater
Update(ctx context.Context, updatedScene *models.Scene) error
Updater
type ImporterReaderWriter interface {
models.SceneCreatorUpdater
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Scene, error)
}
type Importer struct {
ReaderWriter FullCreatorUpdater
FileFinder file.Getter
StudioWriter studio.NameFinderCreator
GalleryFinder gallery.Finder
PerformerWriter performer.NameFinderCreator
MovieWriter movie.NameFinderCreator
TagWriter tag.NameFinderCreator
ReaderWriter ImporterReaderWriter
FileFinder models.FileFinder
StudioWriter models.StudioFinderCreator
GalleryFinder models.GalleryFinder
PerformerWriter models.PerformerFinderCreator
MovieWriter models.MovieFinderCreator
TagWriter models.TagFinderCreator
Input jsonschema.Scene
MissingRefBehaviour models.ImportMissingRefEnum
FileNamingAlgorithm models.HashAlgorithm
@@ -123,7 +116,7 @@ func (i *Importer) sceneJSONToScene(sceneJSON jsonschema.Scene) models.Scene {
}
func (i *Importer) populateFiles(ctx context.Context) error {
files := make([]*file.VideoFile, 0)
files := make([]*models.VideoFile, 0)
for _, ref := range i.Input.Files {
path := ref
@@ -135,7 +128,7 @@ func (i *Importer) populateFiles(ctx context.Context) error {
if f == nil {
return fmt.Errorf("scene file '%s' not found", path)
} else {
files = append(files, f.(*file.VideoFile))
files = append(files, f.(*models.VideoFile))
}
}
@@ -413,7 +406,7 @@ func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
var fileIDs []file.ID
var fileIDs []models.FileID
for _, f := range i.scene.Files.List() {
fileIDs = append(fileIDs, f.Base().ID)
}
@@ -437,7 +430,7 @@ func (i *Importer) Update(ctx context.Context, id int) error {
return nil
}
func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
tags, err := tagWriter.FindByNames(ctx, names, false)
if err != nil {
return nil, err
@@ -472,7 +465,7 @@ func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []st
return tags, nil
}
func createTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string) ([]*models.Tag, error) {
func createTags(ctx context.Context, tagWriter models.TagCreator, names []string) ([]*models.Tag, error) {
var ret []*models.Tag
for _, name := range names {
newTag := models.NewTag(name)

View File

@@ -7,20 +7,17 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/tag"
)
type MarkerCreatorUpdater interface {
Create(ctx context.Context, newSceneMarker *models.SceneMarker) error
Update(ctx context.Context, updatedSceneMarker *models.SceneMarker) error
models.SceneMarkerCreatorUpdater
FindBySceneID(ctx context.Context, sceneID int) ([]*models.SceneMarker, error)
UpdateTags(ctx context.Context, markerID int, tagIDs []int) error
}
type MarkerImporter struct {
SceneID int
ReaderWriter MarkerCreatorUpdater
TagWriter tag.NameFinderCreator
TagWriter models.TagFinderCreator
Input jsonschema.SceneMarker
MissingRefBehaviour models.ImportMissingRefEnum

View File

@@ -7,15 +7,7 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type MarkerQueryer interface {
Query(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) ([]*models.SceneMarker, int, error)
}
type MarkerCountQueryer interface {
QueryCount(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) (int, error)
}
func MarkerCountByTagID(ctx context.Context, r MarkerCountQueryer, id int, depth *int) (int, error) {
func MarkerCountByTagID(ctx context.Context, r models.SceneMarkerQueryer, id int, depth *int) (int, error) {
filter := &models.SceneMarkerFilterType{
Tags: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"os"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@@ -33,7 +32,7 @@ func (s *Service) Merge(ctx context.Context, sourceIDs []int, destinationID int,
return fmt.Errorf("finding source scenes: %w", err)
}
var fileIDs []file.ID
var fileIDs []models.FileID
for _, src := range sources {
// TODO - delete generated files as needed

Some files were not shown because too many files have changed in this diff Show More