1
Fork 0

get initial scanner up and running

This commit is contained in:
viktorstrate 2020-12-22 01:14:43 +01:00
parent 732ccd05ab
commit aeb05bca49
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
12 changed files with 211 additions and 59 deletions

View File

@ -215,6 +215,8 @@ type AlbumResolver interface {
Media(ctx context.Context, obj *models.Album, filter *models.Filter, onlyFavorites *bool) ([]*models.Media, error)
SubAlbums(ctx context.Context, obj *models.Album, filter *models.Filter) ([]*models.Album, error)
Owner(ctx context.Context, obj *models.Album) (*models.User, error)
Thumbnail(ctx context.Context, obj *models.Album) (*models.Media, error)
Path(ctx context.Context, obj *models.Album) ([]*models.Album, error)
Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error)
@ -2366,14 +2368,14 @@ func (ec *executionContext) _Album_owner(ctx context.Context, field graphql.Coll
Object: "Album",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Owner, nil
return ec.resolvers.Album().Owner(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@ -2385,9 +2387,9 @@ func (ec *executionContext) _Album_owner(ctx context.Context, field graphql.Coll
}
return graphql.Null
}
res := resTmp.(models.User)
res := resTmp.(*models.User)
fc.Result = res
return ec.marshalNUser2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
return ec.marshalNUser2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Album_filePath(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
@ -7534,10 +7536,19 @@ func (ec *executionContext) _Album(ctx context.Context, sel ast.SelectionSet, ob
case "parentAlbum":
out.Values[i] = ec._Album_parentAlbum(ctx, field, obj)
case "owner":
out.Values[i] = ec._Album_owner(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Album_owner(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "filePath":
out.Values[i] = ec._Album_filePath(ctx, field, obj)
if out.Values[i] == graphql.Null {

View File

@ -12,10 +12,11 @@ type Album struct {
Title string `gorm:"not null"`
ParentAlbumID *int
ParentAlbum *Album
OwnerID int `gorm:"not null"`
Owner User
Path string `gorm:"not null"`
PathHash string `gorm:"unique"`
// OwnerID int `gorm:"not null"`
// Owner User
Owners []User `gorm:"many2many:user_albums"`
Path string `gorm:"not null"`
PathHash string `gorm:"unique"`
}
func (a *Album) FilePath() string {

View File

@ -153,3 +153,30 @@ func VerifyTokenAndGetUser(db *gorm.DB, token string) (*User, error) {
return &user, nil
}
// FillAlbums fill user.Albums with albums from database
func (user *User) FillAlbums(db *gorm.DB) error {
// Albums already present
if len(user.Albums) > 0 {
return nil
}
if err := db.Model(&user).Association("Albums").Find(&user.Albums); err != nil {
return errors.Wrap(err, "fill user albums")
}
return nil
}
func (user *User) OwnsAlbum(db *gorm.DB, album *Album) (bool, error) {
// user.QueryUserAlbums(db, db.Where("id = ?", album.ID))
// TODO: Implement this
return true, nil
}
func (user *User) OwnsMedia(db *gorm.DB, media *Media) (bool, error) {
// TODO: implement this
return true, nil
}

View File

@ -2,10 +2,12 @@ package resolvers
import (
"context"
"errors"
api "github.com/photoview/photoview/api/graphql"
"github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models"
"gorm.io/gorm"
)
func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) ([]*models.Album, error) {
@ -14,10 +16,19 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onl
return nil, auth.ErrUnauthorized
}
query := r.Database.Where("owner_id = ?", user.ID)
if err := user.FillAlbums(r.Database); err != nil {
return nil, err
}
userAlbumIDs := make([]int, len(user.Albums))
for i, album := range user.Albums {
userAlbumIDs[i] = album.ID
}
query := r.Database.Model(models.Album{}).Where("id IN (?)", userAlbumIDs)
if onlyRoot != nil && *onlyRoot == true {
query = query.Where("parent_album_id = (?)", r.Database.Model(&models.Album{}).Select("id").Where("parent_album_id IS NULL AND owner_id = ?", user.ID))
query = query.Where("parent_album_id = (?)", r.Database.Model(&models.Album{}).Select("id").Where("parent_album_id IS NULL"))
}
if showEmpty == nil || *showEmpty == false {
@ -33,7 +44,7 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onl
query = filter.FormatSQL(query)
var albums []*models.Album
if err := query.Find(&albums).Error; err != nil {
if err := query.Scan(&albums).Error; err != nil {
return nil, err
}
@ -47,10 +58,22 @@ func (r *queryResolver) Album(ctx context.Context, id int) (*models.Album, error
}
var album models.Album
if err := r.Database.Where("owner_id = ?", user.ID).First(&album, id).Error; err != nil {
if err := r.Database.First(&album, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("album not found")
}
return nil, err
}
ownsAlbum, err := user.OwnsAlbum(r.Database, &album)
if err != nil {
return nil, err
}
if !ownsAlbum {
return nil, errors.New("forbidden")
}
return &album, nil
}
@ -154,8 +177,24 @@ func (r *albumResolver) Path(ctx context.Context, obj *models.Album) ([]*models.
UNION
SELECT parent.* FROM path_albums child JOIN albums parent ON parent.id = child.parent_album_id
)
SELECT * FROM path_albums WHERE id != ? AND owner_id = ?
`, obj.ID, obj.ID, user.ID).Scan(&album_path).Error
SELECT * FROM path_albums WHERE id != ?
`, obj.ID, obj.ID).Scan(&album_path).Error
// Make sure to only return albums this user owns
for i := len(album_path) - 1; i >= 0; i-- {
album := album_path[i]
owns, err := user.OwnsAlbum(r.Database, album)
if err != nil {
return nil, err
}
if !owns {
album_path = album_path[i+1:]
break
}
}
if err != nil {
return nil, err

View File

@ -18,11 +18,20 @@ func (r *queryResolver) MyMedia(ctx context.Context, filter *models.Filter) ([]*
return nil, errors.New("unauthorized")
}
if err := user.FillAlbums(r.Database); err != nil {
return nil, err
}
userAlbumIDs := make([]int, len(user.Albums))
for i, album := range user.Albums {
userAlbumIDs[i] = album.ID
}
var media []*models.Media
query := r.Database.
Joins("Album").
Where("albums.owner_id = ?", user.ID).
Where("albums.id IN (?)", userAlbumIDs).
Where("media.id IN (?)", r.Database.Model(&models.MediaURL{}).Select("id").Where("media_url.media_id = media.id"))
query = filter.FormatSQL(query)

View File

@ -28,7 +28,6 @@ func (r *mutationResolver) ScanAll(ctx context.Context) (*models.ScannerResult,
func (r *mutationResolver) ScanUser(ctx context.Context, userID int) (*models.ScannerResult, error) {
var user models.User
if err := r.Database.First(&user, userID).Error; err != nil {
return nil, errors.Wrap(err, "get user from database")
}

View File

@ -5,6 +5,7 @@ import (
"github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/scanner"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
@ -84,6 +85,11 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
return err
}
_, err = scanner.NewRootAlbum(tx, rootPath, user)
if err != nil {
return err
}
token, err = user.GenerateAccessToken(tx)
if err != nil {
return err

View File

@ -19,7 +19,12 @@ func authenticateMedia(media *models.Media, db *gorm.DB, r *http.Request) (succe
return false, "internal server error", http.StatusInternalServerError, err
}
if album.OwnerID != user.ID {
ownsAlbum, err := user.OwnsAlbum(db, &album)
if err != nil {
return false, "internal server error", http.StatusInternalServerError, err
}
if !ownsAlbum {
return false, "invalid credentials", http.StatusForbidden, nil
}
} else {

View File

@ -4,7 +4,6 @@ import (
"os"
"path"
"strconv"
"strings"
"github.com/photoview/photoview/api/graphql/models"
"github.com/pkg/errors"
@ -59,28 +58,32 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model
return nil
}
albumPaths := make([]interface{}, len(scannedAlbums))
scannedAlbumIDs := make([]interface{}, len(scannedAlbums))
for i, album := range scannedAlbums {
albumPaths[i] = album.Path
scannedAlbumIDs[i] = album.ID
}
// Delete old albums
album_args := make([]interface{}, 0)
album_args = append(album_args, user.ID)
album_args = append(album_args, albumPaths...)
var albums []models.Album
albums_questions := strings.Repeat("MD5(?),", len(albumPaths))[:len(albumPaths)*7-1]
if err := db.Where("owner_id = ? AND path_hash NOT IN ("+albums_questions+")", album_args...).Find(&albums).Error; err != nil {
userAlbumIDs := make([]int, len(user.Albums))
for i, album := range user.Albums {
userAlbumIDs[i] = album.ID
}
query := db.
Where("id IN (?)", userAlbumIDs).
Where("id NOT IN (?)", scannedAlbumIDs)
if err := query.Find(&albums).Error; err != nil {
return []error{errors.Wrap(err, "get albums to be deleted from database")}
}
deleteErrors := make([]error, 0)
albumIDs := make([]int, 0)
for _, album := range albums {
albumIDs = append(albumIDs, album.ID)
deleteAlbumIDs := make([]int, len(albums))
for i, album := range albums {
deleteAlbumIDs[i] = album.ID
cachePath := path.Join(PhotoCache(), strconv.Itoa(int(album.ID)))
err := os.RemoveAll(cachePath)
if err != nil {
@ -88,7 +91,7 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model
}
}
if err := db.Where("id IN ?", albumIDs).Delete(models.Album{}).Error; err != nil {
if err := db.Where("id IN ?", deleteAlbumIDs).Delete(models.Album{}).Error; err != nil {
ScannerError("Could not delete old albums from database:\n%s\n", err)
deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old albums from database"))
}

View File

@ -13,6 +13,25 @@ import (
"gorm.io/gorm"
)
func NewRootAlbum(db *gorm.DB, rootPath string, owner *models.User) (*models.Album, error) {
owners := []models.User{
*owner,
}
album := models.Album{
Title: path.Base(rootPath),
Path: rootPath,
Owners: owners,
}
if err := db.Create(&album).Error; err != nil {
return nil, err
}
return &album, nil
}
func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
album_notify_key := utils.GenerateToken()

View File

@ -17,16 +17,25 @@ import (
func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScannerCache) ([]*models.Album, []error) {
if err := user.FillAlbums(db); err != nil {
return nil, []error{err}
}
userAlbumIDs := make([]int, len(user.Albums))
for i, album := range user.Albums {
userAlbumIDs[i] = album.ID
}
var userRootAlbums []*models.Album
if err := db.Model(&user).Association("Albums").Find(&userRootAlbums); err != nil {
return nil, []error{errors.Wrapf(err, "get albums of user (%s)", user.Username)}
if err := db.Where("id IN (?)", userAlbumIDs).Where("parent_album_id IS NULL").Find(&userRootAlbums).Error; err != nil {
return nil, []error{err}
}
scanErrors := make([]error, 0)
type scanInfo struct {
path string
parentID *int
path string
parent *models.Album
}
scanQueue := list.New()
@ -41,8 +50,8 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
}
} else {
scanQueue.PushBack(scanInfo{
path: album.Path,
parentID: nil,
path: album.Path,
parent: nil,
})
}
}
@ -54,7 +63,7 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
scanQueue.Remove(scanQueue.Front())
albumPath := albumInfo.path
albumParentID := albumInfo.parentID
albumParent := albumInfo.parent
// Read path
dirContent, err := ioutil.ReadDir(albumPath)
@ -64,26 +73,50 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
}
// Will become new album or album from db
var album models.Album
var album *models.Album
transErr := db.Transaction(func(tx *gorm.DB) error {
log.Printf("Scanning directory: %s", albumPath)
// Make album if not exists
albumTitle := path.Base(albumPath)
err = tx.FirstOrCreate(&album, models.Album{
Title: albumTitle,
ParentAlbumID: albumParentID,
OwnerID: user.ID,
Path: albumPath,
}).Error
if err != nil {
return errors.Wrap(err, "insert album into database")
// check if album already exists
var albumResult []models.Album
result := tx.Where("path_hash = md5(?)", albumPath).Find(&albumResult)
if result.Error != nil {
return result.Error
}
userAlbums = append(userAlbums, &album)
// album does not exist, create new
if len(albumResult) == 0 {
albumTitle := path.Base(albumPath)
var albumParentID *int
parentOwners := make([]models.User, 0)
if albumParent != nil {
albumParentID = &albumParent.ID
if err := db.Model(&albumParent).Association("Owners").Find(&parentOwners); err != nil {
return err
}
}
album = &models.Album{
Title: albumTitle,
ParentAlbumID: albumParentID,
Path: albumPath,
}
if err := tx.Create(&album).Error; err != nil {
return errors.Wrap(err, "insert album into database")
}
if err := tx.Model(&album).Association("Owners").Append(parentOwners); err != nil {
return errors.Wrap(err, "add owners to album")
}
} else {
album = &albumResult[0]
}
userAlbums = append(userAlbums, album)
return nil
})
@ -104,8 +137,8 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
if item.IsDir() && directoryContainsPhotos(subalbumPath, album_cache) {
scanQueue.PushBack(scanInfo{
path: subalbumPath,
parentID: &album.ID,
path: subalbumPath,
parent: album,
})
}
}

View File

@ -11,7 +11,7 @@ const USERS_QUERY = gql`
user {
id
username
rootPath
# rootPath
admin
}
}