mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 21:59:12 +00:00
Replace database, mostly album related
This commit is contained in:
@@ -71,7 +71,13 @@ func SetupDatabase() (*gorm.DB, error) {
|
||||
|
||||
func MigrateDatabase(db *gorm.DB) error {
|
||||
|
||||
db.AutoMigrate(&models.User{}, &models.AccessToken{})
|
||||
db.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.AccessToken{},
|
||||
&models.SiteInfo{},
|
||||
&models.Album{},
|
||||
&models.MediaEXIF{},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,48 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Album struct {
|
||||
AlbumID int
|
||||
gorm.Model
|
||||
Title string
|
||||
ParentAlbum *int
|
||||
OwnerID int
|
||||
Owner User
|
||||
Path string
|
||||
PathHash string
|
||||
}
|
||||
|
||||
func (a *Album) ID() int {
|
||||
return a.AlbumID
|
||||
}
|
||||
|
||||
func (a *Album) FilePath() string {
|
||||
return a.Path
|
||||
}
|
||||
|
||||
func NewAlbumFromRow(row *sql.Row) (*Album, error) {
|
||||
album := Album{}
|
||||
|
||||
if err := row.Scan(&album.AlbumID, &album.Title, &album.ParentAlbum, &album.OwnerID, &album.Path, &album.PathHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &album, nil
|
||||
}
|
||||
|
||||
func NewAlbumsFromRows(rows *sql.Rows) ([]*Album, error) {
|
||||
albums := make([]*Album, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var album Album
|
||||
if err := rows.Scan(&album.AlbumID, &album.Title, &album.ParentAlbum, &album.OwnerID, &album.Path, &album.PathHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
albums = append(albums, &album)
|
||||
}
|
||||
|
||||
rows.Close()
|
||||
|
||||
return albums, nil
|
||||
}
|
||||
|
||||
@@ -52,13 +52,13 @@ type SearchResult struct {
|
||||
}
|
||||
|
||||
// General information about the site
|
||||
type SiteInfo struct {
|
||||
InitialSetup bool `json:"initialSetup"`
|
||||
// How often automatic scans should be initiated in seconds
|
||||
PeriodicScanInterval int `json:"periodicScanInterval"`
|
||||
// How many max concurrent scanner jobs that should run at once
|
||||
ConcurrentWorkers int `json:"concurrentWorkers"`
|
||||
}
|
||||
// type SiteInfo struct {
|
||||
// InitialSetup bool `json:"initialSetup"`
|
||||
// // How often automatic scans should be initiated in seconds
|
||||
// PeriodicScanInterval int `json:"periodicScanInterval"`
|
||||
// // How many max concurrent scanner jobs that should run at once
|
||||
// ConcurrentWorkers int `json:"concurrentWorkers"`
|
||||
// }
|
||||
|
||||
type MediaType string
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MediaEXIF struct {
|
||||
ExifID int
|
||||
gorm.Model
|
||||
Camera *string
|
||||
Maker *string
|
||||
Lens *string
|
||||
@@ -21,21 +22,3 @@ type MediaEXIF struct {
|
||||
GPSLatitude *float64
|
||||
GPSLonitude *float64
|
||||
}
|
||||
|
||||
func (exif *MediaEXIF) Media() *Media {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (exif *MediaEXIF) ID() int {
|
||||
return exif.ExifID
|
||||
}
|
||||
|
||||
func NewMediaExifFromRow(row *sql.Row) (*MediaEXIF, error) {
|
||||
exif := MediaEXIF{}
|
||||
|
||||
if err := row.Scan(&exif.ExifID, &exif.Camera, &exif.Maker, &exif.Lens, &exif.DateShot, &exif.Exposure, &exif.Aperture, &exif.Iso, &exif.FocalLength, &exif.Flash, &exif.Orientation, &exif.ExposureProgram, &exif.GPSLatitude, &exif.GPSLonitude); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &exif, nil
|
||||
}
|
||||
|
||||
@@ -5,55 +5,26 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// func initializeSiteInfoRow(db *gorm.DB) (*SiteInfo, error) {
|
||||
// _, err := db.Exec("INSERT INTO site_info (initial_setup, periodic_scan_interval, concurrent_workers) VALUES (true, 0, 3)")
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "initialize site_info row")
|
||||
// }
|
||||
|
||||
// siteInfo := &SiteInfo{}
|
||||
|
||||
// row := db.QueryRow("SELECT * FROM site_info")
|
||||
// if err := row.Scan(&siteInfo.InitialSetup, &siteInfo.PeriodicScanInterval, &siteInfo.ConcurrentWorkers); err != nil {
|
||||
// return nil, errors.Wrap(err, "get site_info row after initialization")
|
||||
// }
|
||||
|
||||
// return siteInfo, nil
|
||||
// }
|
||||
type SiteInfo struct {
|
||||
gorm.Model
|
||||
InitialSetup bool
|
||||
PeriodicScanInterval int
|
||||
ConcurrentWorkers int
|
||||
}
|
||||
|
||||
// GetSiteInfo gets the site info row from the database, and creates it if it does not exist
|
||||
func GetSiteInfo(db *gorm.DB) (*SiteInfo, error) {
|
||||
|
||||
var siteInfo SiteInfo
|
||||
|
||||
result := db.FirstOrCreate(&siteInfo, SiteInfo{
|
||||
err := db.FirstOrCreate(&siteInfo, SiteInfo{
|
||||
InitialSetup: true,
|
||||
PeriodicScanInterval: 0,
|
||||
ConcurrentWorkers: 3,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return nil, errors.Wrap(result.Error, "get site info from database")
|
||||
}).Error
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get site info from database")
|
||||
}
|
||||
|
||||
// rows, err := db.Query("SELECT * FROM site_info")
|
||||
// defer rows.Close()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// siteInfo := &SiteInfo{}
|
||||
|
||||
// if !rows.Next() {
|
||||
// // Entry does not exist
|
||||
// siteInfo, err = initializeSiteInfoRow(db)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// } else {
|
||||
// if err := rows.Scan(&siteInfo.InitialSetup, &siteInfo.PeriodicScanInterval, &siteInfo.ConcurrentWorkers); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
|
||||
return &siteInfo, nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error {
|
||||
func CleanupMedia(db *gorm.DB, albumId uint, albumMedia []*models.Media) []error {
|
||||
albumMediaIds := make([]interface{}, len(albumMedia))
|
||||
for i, photo := range albumMedia {
|
||||
albumMediaIds[i] = photo.MediaID
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -18,14 +19,13 @@ type periodicScanner struct {
|
||||
var mainPeriodicScanner *periodicScanner = nil
|
||||
|
||||
func getPeriodicScanInterval(db *gorm.DB) (time.Duration, error) {
|
||||
row := db.QueryRow("SELECT periodic_scan_interval FROM site_info")
|
||||
var intervalSeconds int
|
||||
|
||||
if err := row.Scan(&intervalSeconds); err != nil {
|
||||
var siteInfo models.SiteInfo
|
||||
if err := db.First(&siteInfo).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return time.Duration(intervalSeconds) * time.Second, nil
|
||||
return time.Duration(siteInfo.PeriodicScanInterval) * time.Second, nil
|
||||
}
|
||||
|
||||
func InitializePeriodicScanner(db *gorm.DB) error {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
"github.com/viktorstrate/photoview/api/utils"
|
||||
"gorm.io/gorm"
|
||||
|
||||
// Image decoders
|
||||
_ "image/gif"
|
||||
@@ -42,7 +43,7 @@ func makePhotoURLChecker(tx *sql.Tx, mediaID int) (func(purpose models.MediaPurp
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ProcessMedia(tx *sql.Tx, media *models.Media) (bool, error) {
|
||||
func ProcessMedia(tx *gorm.DB, media *models.Media) (bool, error) {
|
||||
imageData := EncodeMediaData{
|
||||
media: media,
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func ProcessMedia(tx *sql.Tx, media *models.Media) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func processPhoto(tx *sql.Tx, imageData *EncodeMediaData, photoCachePath *string) (bool, error) {
|
||||
func processPhoto(tx *gorm.DB, imageData *EncodeMediaData, photoCachePath *string) (bool, error) {
|
||||
|
||||
photo := imageData.media
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ func (queue *ScannerQueue) jobOnQueue(job *ScannerJob) (bool, error) {
|
||||
scannerJobs := append(queue.in_progress, queue.up_next...)
|
||||
|
||||
for _, scannerJob := range scannerJobs {
|
||||
if scannerJob.album.AlbumID == job.album.AlbumID {
|
||||
if scannerJob.album.ID == job.album.ID {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
"github.com/viktorstrate/photoview/api/graphql/notification"
|
||||
"github.com/viktorstrate/photoview/api/utils"
|
||||
@@ -37,37 +38,35 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
|
||||
|
||||
album_has_changes := false
|
||||
for count, photo := range albumPhotos {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
ScannerError("Failed to begin database transaction: %s", err)
|
||||
}
|
||||
// tx, err := db.Begin()
|
||||
|
||||
processing_was_needed, err := ProcessMedia(tx, photo)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
ScannerError("Failed to process photo (%s): %s", photo.Path, err)
|
||||
continue
|
||||
}
|
||||
transactionResult := db.Transaction(func(tx *gorm.DB) error {
|
||||
processing_was_needed, err := ProcessMedia(tx, photo)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to process photo (%s)", photo.Path)
|
||||
}
|
||||
|
||||
if processing_was_needed {
|
||||
album_has_changes = true
|
||||
progress := float64(count) / float64(len(albumPhotos)) * 100.0
|
||||
notification.BroadcastNotification(&models.Notification{
|
||||
Key: album_notify_key,
|
||||
Type: models.NotificationTypeProgress,
|
||||
Header: fmt.Sprintf("Processing media for album '%s'", album.Title),
|
||||
Content: fmt.Sprintf("Processed media at %s", photo.Path),
|
||||
Progress: &progress,
|
||||
})
|
||||
}
|
||||
if processing_was_needed {
|
||||
album_has_changes = true
|
||||
progress := float64(count) / float64(len(albumPhotos)) * 100.0
|
||||
notification.BroadcastNotification(&models.Notification{
|
||||
Key: album_notify_key,
|
||||
Type: models.NotificationTypeProgress,
|
||||
Header: fmt.Sprintf("Processing media for album '%s'", album.Title),
|
||||
Content: fmt.Sprintf("Processed media at %s", photo.Path),
|
||||
Progress: &progress,
|
||||
})
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
ScannerError("Failed to commit database transaction: %s", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
if transactionResult.Error != nil {
|
||||
ScannerError("Failed to begin database transaction: %s", transactionResult.Error)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_errors := CleanupMedia(db, album.AlbumID, albumPhotos)
|
||||
cleanup_errors := CleanupMedia(db, album.ID, albumPhotos)
|
||||
for _, err := range cleanup_errors {
|
||||
ScannerError("Failed to delete old media: %s", err)
|
||||
}
|
||||
@@ -98,24 +97,20 @@ func findMediaForAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.D
|
||||
photoPath := path.Join(album.Path, item.Name())
|
||||
|
||||
if !item.IsDir() && isPathMedia(photoPath, cache) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
ScannerError("Could not begin database transaction for image %s: %s\n", photoPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
photo, isNewPhoto, err := ScanMedia(tx, photoPath, album.AlbumID, cache)
|
||||
if err != nil {
|
||||
ScannerError("Scanning media error (%s): %s", photoPath, err)
|
||||
tx.Rollback()
|
||||
continue
|
||||
}
|
||||
db.Transaction(func(tx *gorm.DB) error {
|
||||
photo, isNewPhoto, err := ScanMedia(tx, photoPath, album.ID, cache)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Scanning media error (%s)", photoPath)
|
||||
}
|
||||
|
||||
onScanPhoto(photo, isNewPhoto)
|
||||
onScanPhoto(photo, isNewPhoto)
|
||||
|
||||
albumPhotos = append(albumPhotos, photo)
|
||||
albumPhotos = append(albumPhotos, photo)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
tx.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func scanForSideCarFile(path string) *string {
|
||||
@@ -47,7 +48,7 @@ func hashSideCarFile(path *string) *string {
|
||||
return &hash
|
||||
}
|
||||
|
||||
func ScanMedia(tx *sql.Tx, mediaPath string, albumId int, cache *AlbumScannerCache) (*models.Media, bool, error) {
|
||||
func ScanMedia(tx *gorm.DB, mediaPath string, albumId uint, cache *AlbumScannerCache) (*models.Media, bool, error) {
|
||||
mediaName := path.Base(mediaPath)
|
||||
|
||||
// Check if image already exists
|
||||
|
||||
Reference in New Issue
Block a user