Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add partial import functionality #812

Merged
merged 45 commits into from
Sep 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
310df19
Update dependencies
WithoutPants Aug 25, 2020
3f578b1
Add dependencies
WithoutPants Aug 26, 2020
c16bec9
Add target to generate mocks
WithoutPants Aug 26, 2020
d124a0a
Refactor tag export
WithoutPants Aug 26, 2020
9a882d7
Refactor movie export
WithoutPants Aug 26, 2020
e91d3e9
Refactor studio export
WithoutPants Aug 26, 2020
84a4687
Refactor performer export
WithoutPants Aug 26, 2020
f3cb083
Refactor scene export
WithoutPants Aug 26, 2020
ed9e859
Add DownloadStore
WithoutPants Aug 28, 2020
abd8623
Add missing context key
WithoutPants Aug 28, 2020
ac0d4f5
Add missing functions to models
WithoutPants Aug 28, 2020
cb1a45c
Add missing functions to models
WithoutPants Aug 28, 2020
d521694
Support exporting subset of objects
WithoutPants Aug 28, 2020
1f1f06b
Add includeDependencies functionality
WithoutPants Aug 28, 2020
c06cded
Refactor dependency code
WithoutPants Aug 28, 2020
928eced
Add export scenes to UI
WithoutPants Aug 28, 2020
cb89473
Make showWhenSelected generic
WithoutPants Aug 28, 2020
6a03cb7
Lint and format
WithoutPants Aug 28, 2020
8e50d1a
Update mocks
WithoutPants Aug 28, 2020
1cc0d09
Lint
WithoutPants Aug 28, 2020
43e5107
Remove unused functions
WithoutPants Aug 29, 2020
88acfe4
Merge remote-tracking branch 'upstream/develop' into import-export
WithoutPants Aug 29, 2020
77016d1
Remove superfluous temp dir creation
WithoutPants Aug 31, 2020
ead93fd
Add timestamp suffix to zip link
WithoutPants Aug 31, 2020
420ceda
Add importObjects mutation
WithoutPants Sep 3, 2020
fe647c2
Add import performer code
WithoutPants Sep 3, 2020
b518a24
Refactor import. Add tag importer
WithoutPants Sep 4, 2020
2fcd350
Refactor performer importer
WithoutPants Sep 4, 2020
49d0613
Add studio importer
WithoutPants Sep 4, 2020
5bfd238
Add movie importer
WithoutPants Sep 5, 2020
3c3bae7
Add gallery importer
WithoutPants Sep 5, 2020
2f8a84c
Add scene importer
WithoutPants Sep 8, 2020
dffd4d0
Add unit tests
WithoutPants Sep 8, 2020
301a6cc
Remove logging error for empty strings
WithoutPants Sep 15, 2020
90a362f
Bug fixing
WithoutPants Sep 15, 2020
6ced9e9
Add import dialog
WithoutPants Sep 15, 2020
044755e
Merge remote-tracking branch 'upstream/develop' into import-enhance
WithoutPants Sep 15, 2020
89480b4
Bug fixing
WithoutPants Sep 15, 2020
4707592
Add UI support for file upload
WithoutPants Sep 15, 2020
da1007d
Implement file upload import
WithoutPants Sep 15, 2020
be9f9e7
Disable import if no file selected
WithoutPants Sep 15, 2020
472ff8f
Merge remote-tracking branch 'upstream/develop' into import-enhance
WithoutPants Sep 15, 2020
4b80929
Lint and format
WithoutPants Sep 15, 2020
6efc525
Merge remote-tracking branch 'upstream/develop' into import-enhance
WithoutPants Sep 20, 2020
a509197
Add changelog entry
WithoutPants Sep 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions graphql/documents/mutations/metadata.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ mutation ExportObjects($input: ExportObjectsInput!) {
exportObjects(input: $input)
}

mutation ImportObjects($input: ImportObjectsInput!) {
importObjects(input: $input)
}

mutation MetadataScan($input: ScanMetadataInput!) {
metadataScan(input: $input)
}
Expand Down
7 changes: 5 additions & 2 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,12 @@ type Mutation {
"""Returns a link to download the result"""
exportObjects(input: ExportObjectsInput!): String

"""Start an import. Returns the job ID"""
"""Performs an incremental import. Returns the job ID"""
importObjects(input: ImportObjectsInput!): String!

"""Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID"""
metadataImport: String!
"""Start an export. Returns the job ID"""
"""Start a full export. Outputs to the metadata directory. Returns the job ID"""
metadataExport: String!
"""Start a scan. Returns the job ID"""
metadataScan(input: ScanMetadataInput!): String!
Expand Down
20 changes: 20 additions & 0 deletions graphql/schema/types/metadata.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
scalar Upload

input GenerateMetadataInput {
sprites: Boolean!
previews: Boolean!
Expand Down Expand Up @@ -65,3 +67,21 @@ input ExportObjectsInput {
galleries: ExportObjectTypeInput
includeDependencies: Boolean
}

enum ImportDuplicateEnum {
IGNORE
OVERWRITE
FAIL
}

enum ImportMissingRefEnum {
IGNORE
FAIL
CREATE
}

input ImportObjectsInput {
file: Upload!
duplicateBehaviour: ImportDuplicateEnum!
missingRefBehaviour: ImportMissingRefEnum!
}
10 changes: 10 additions & 0 deletions pkg/api/resolver_mutation_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ func (r *mutationResolver) MetadataImport(ctx context.Context) (string, error) {
return "todo", nil
}

func (r *mutationResolver) ImportObjects(ctx context.Context, input models.ImportObjectsInput) (string, error) {
t := manager.CreateImportTask(config.GetVideoFileNamingAlgorithm(), input)
_, err := manager.GetInstance().RunSingleTask(t)
if err != nil {
return "", err
}

return "todo", nil
}

func (r *mutationResolver) MetadataExport(ctx context.Context) (string, error) {
manager.GetInstance().Export()
return "todo", nil
Expand Down
72 changes: 72 additions & 0 deletions pkg/gallery/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package gallery

import (
"fmt"
"time"

"github.com/stashapp/stash/pkg/manager/jsonschema"
"github.com/stashapp/stash/pkg/models"
)

type Importer struct {
ReaderWriter models.GalleryReaderWriter
Input jsonschema.PathMapping

gallery models.Gallery
imageData []byte
}

func (i *Importer) PreImport() error {
currentTime := time.Now()
i.gallery = models.Gallery{
Checksum: i.Input.Checksum,
Path: i.Input.Path,
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
}

return nil
}

func (i *Importer) PostImport(id int) error {
return nil
}

func (i *Importer) Name() string {
return i.Input.Path
}

func (i *Importer) FindExistingID() (*int, error) {
existing, err := i.ReaderWriter.FindByPath(i.Name())
if err != nil {
return nil, err
}

if existing != nil {
id := existing.ID
return &id, nil
}

return nil, nil
}

func (i *Importer) Create() (*int, error) {
created, err := i.ReaderWriter.Create(i.gallery)
if err != nil {
return nil, fmt.Errorf("error creating gallery: %s", err.Error())
}

id := created.ID
return &id, nil
}

func (i *Importer) Update(id int) error {
gallery := i.gallery
gallery.ID = id
_, err := i.ReaderWriter.Update(gallery)
if err != nil {
return fmt.Errorf("error updating existing gallery: %s", err.Error())
}

return nil
}
147 changes: 147 additions & 0 deletions pkg/gallery/import_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package gallery

import (
"errors"
"testing"

"github.com/stashapp/stash/pkg/manager/jsonschema"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/mocks"
"github.com/stretchr/testify/assert"
)

const (
galleryPath = "galleryPath"
galleryPathErr = "galleryPathErr"
existingGalleryPath = "existingGalleryPath"

galleryID = 1
idErr = 2
existingGalleryID = 100
)

func TestImporterName(t *testing.T) {
i := Importer{
Input: jsonschema.PathMapping{
Path: galleryPath,
},
}

assert.Equal(t, galleryPath, i.Name())
}

func TestImporterPreImport(t *testing.T) {
i := Importer{
Input: jsonschema.PathMapping{
Path: galleryPath,
},
}

err := i.PreImport()
assert.Nil(t, err)
}

func TestImporterFindExistingID(t *testing.T) {
readerWriter := &mocks.GalleryReaderWriter{}

i := Importer{
ReaderWriter: readerWriter,
Input: jsonschema.PathMapping{
Path: galleryPath,
},
}

errFindByPath := errors.New("FindByPath error")
readerWriter.On("FindByPath", galleryPath).Return(nil, nil).Once()
readerWriter.On("FindByPath", existingGalleryPath).Return(&models.Gallery{
ID: existingGalleryID,
}, nil).Once()
readerWriter.On("FindByPath", galleryPathErr).Return(nil, errFindByPath).Once()

id, err := i.FindExistingID()
assert.Nil(t, id)
assert.Nil(t, err)

i.Input.Path = existingGalleryPath
id, err = i.FindExistingID()
assert.Equal(t, existingGalleryID, *id)
assert.Nil(t, err)

i.Input.Path = galleryPathErr
id, err = i.FindExistingID()
assert.Nil(t, id)
assert.NotNil(t, err)

readerWriter.AssertExpectations(t)
}

func TestCreate(t *testing.T) {
readerWriter := &mocks.GalleryReaderWriter{}

gallery := models.Gallery{
Path: galleryPath,
}

galleryErr := models.Gallery{
Path: galleryPathErr,
}

i := Importer{
ReaderWriter: readerWriter,
gallery: gallery,
}

errCreate := errors.New("Create error")
readerWriter.On("Create", gallery).Return(&models.Gallery{
ID: galleryID,
}, nil).Once()
readerWriter.On("Create", galleryErr).Return(nil, errCreate).Once()

id, err := i.Create()
assert.Equal(t, galleryID, *id)
assert.Nil(t, err)

i.gallery = galleryErr
id, err = i.Create()
assert.Nil(t, id)
assert.NotNil(t, err)

readerWriter.AssertExpectations(t)
}

func TestUpdate(t *testing.T) {
readerWriter := &mocks.GalleryReaderWriter{}

gallery := models.Gallery{
Path: galleryPath,
}

galleryErr := models.Gallery{
Path: galleryPathErr,
}

i := Importer{
ReaderWriter: readerWriter,
gallery: gallery,
}

errUpdate := errors.New("Update error")

// id needs to be set for the mock input
gallery.ID = galleryID
readerWriter.On("Update", gallery).Return(nil, nil).Once()

err := i.Update(galleryID)
assert.Nil(t, err)

i.gallery = galleryErr

// need to set id separately
galleryErr.ID = idErr
readerWriter.On("Update", galleryErr).Return(nil, errUpdate).Once()

err = i.Update(idErr)
assert.NotNil(t, err)

readerWriter.AssertExpectations(t)
}
61 changes: 61 additions & 0 deletions pkg/manager/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package manager

import (
"fmt"

"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)

type importer interface {
PreImport() error
PostImport(id int) error
Name() string
FindExistingID() (*int, error)
Create() (*int, error)
Update(id int) error
}

func performImport(i importer, duplicateBehaviour models.ImportDuplicateEnum) error {
if err := i.PreImport(); err != nil {
return err
}

// try to find an existing object with the same name
name := i.Name()
existing, err := i.FindExistingID()
if err != nil {
return fmt.Errorf("error finding existing objects: %s", err.Error())
}

var id int

if existing != nil {
if duplicateBehaviour == models.ImportDuplicateEnumFail {
return fmt.Errorf("existing object with name '%s'", name)
} else if duplicateBehaviour == models.ImportDuplicateEnumIgnore {
logger.Info("Skipping existing object")
return nil
}

// must be overwriting
id = *existing
if err := i.Update(id); err != nil {
return fmt.Errorf("error updating existing object: %s", err.Error())
}
} else {
// creating
createdID, err := i.Create()
if err != nil {
return fmt.Errorf("error creating object: %s", err.Error())
}

id = *createdID
}

if err := i.PostImport(id); err != nil {
return err
}

return nil
}
3 changes: 2 additions & 1 deletion pkg/manager/jsonschema/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package jsonschema

import (
"bytes"
"github.com/json-iterator/go"

"io/ioutil"
"time"

jsoniter "github.com/json-iterator/go"
)

var nilTime = (time.Time{}).UnixNano()
Expand Down
8 changes: 7 additions & 1 deletion pkg/manager/manager_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,13 @@ func (s *singleton) Import() {

var wg sync.WaitGroup
wg.Add(1)
task := ImportTask{fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm()}
task := ImportTask{
BaseDir: config.GetMetadataPath(),
Reset: true,
DuplicateBehaviour: models.ImportDuplicateEnumFail,
MissingRefBehaviour: models.ImportMissingRefEnumFail,
fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm(),
}
go task.Start(&wg)
wg.Wait()
}()
Expand Down
Loading