Replace database, work on scanning
This commit is contained in:
parent
364521958b
commit
98f13d76e6
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetupDatabase connects to the database using environment variables
|
// SetupDatabase connects to the database using environment variables
|
||||||
|
@ -32,39 +33,17 @@ func SetupDatabase() (*gorm.DB, error) {
|
||||||
|
|
||||||
log.Printf("Connecting to database: %s", address)
|
log.Printf("Connecting to database: %s", address)
|
||||||
|
|
||||||
db, err := gorm.Open(mysql.Open(address.String()), &gorm.Config{})
|
config := gorm.Config{}
|
||||||
|
|
||||||
|
// Enable database debug logging
|
||||||
|
config.Logger = logger.Default.LogMode(logger.Info)
|
||||||
|
|
||||||
|
db, err := gorm.Open(mysql.Open(address.String()), &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not connect to database")
|
return nil, errors.Wrap(err, "could not connect to database")
|
||||||
}
|
}
|
||||||
|
|
||||||
// var db *sql.DB
|
// TODO: Add connection retries
|
||||||
|
|
||||||
// db, err = sql.Open("mysql", address.String())
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, errors.New("Could not connect to database, exiting")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// tryCount := 0
|
|
||||||
|
|
||||||
// for {
|
|
||||||
// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
// defer cancel()
|
|
||||||
|
|
||||||
// if err := db.PingContext(ctx); err != nil {
|
|
||||||
// if tryCount < 4 {
|
|
||||||
// tryCount++
|
|
||||||
// log.Printf("WARN: Could not ping database: %s, Will retry after 1 second", err)
|
|
||||||
// time.Sleep(time.Second)
|
|
||||||
// continue
|
|
||||||
// } else {
|
|
||||||
// return nil, errors.Wrap(err, "Could not ping database, exiting")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
|
|
||||||
// db.SetMaxOpenConns(80)
|
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/viktorstrate/photoview/api/utils"
|
"github.com/viktorstrate/photoview/api/utils"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Media struct {
|
type Media struct {
|
||||||
|
@ -32,6 +35,20 @@ func (Media) TableName() string {
|
||||||
return "media"
|
return "media"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Media) BeforeSave(tx *gorm.DB) error {
|
||||||
|
// Update hashes
|
||||||
|
hash := md5.Sum([]byte(m.Path))
|
||||||
|
m.PathHash = hex.EncodeToString(hash[:])
|
||||||
|
|
||||||
|
if m.SideCarPath != nil {
|
||||||
|
hash = md5.Sum([]byte(*m.SideCarPath))
|
||||||
|
encodedHash := hex.EncodeToString(hash[:])
|
||||||
|
m.SideCarHash = &encodedHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type MediaPurpose string
|
type MediaPurpose string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (r *albumResolver) Media(ctx context.Context, album *models.Album, filter *
|
||||||
query := r.Database.
|
query := r.Database.
|
||||||
Joins("Album").
|
Joins("Album").
|
||||||
Where("Album.id = ?", album.ID).
|
Where("Album.id = ?", album.ID).
|
||||||
Where("media.id IN (?)", r.Database.Model(&models.MediaURL{})).Select("media_id").Where("media_url.media_id = media.id")
|
Where("media.id IN (?)", r.Database.Model(&models.MediaURL{}).Select("media_urls.media_id").Where("media_urls.media_id = media.id"))
|
||||||
|
|
||||||
if onlyFavorites != nil && *onlyFavorites == true {
|
if onlyFavorites != nil && *onlyFavorites == true {
|
||||||
query = query.Where("media.favorite = 1")
|
query = query.Where("media.favorite = 1")
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (r *mediaResolver) Downloads(ctx context.Context, media *models.Media) ([]*
|
||||||
func (r *mediaResolver) HighRes(ctx context.Context, media *models.Media) (*models.MediaURL, error) {
|
func (r *mediaResolver) HighRes(ctx context.Context, media *models.Media) (*models.MediaURL, error) {
|
||||||
var url models.MediaURL
|
var url models.MediaURL
|
||||||
err := r.Database.
|
err := r.Database.
|
||||||
Where("media_url = ?", media.ID).
|
Where("media_id = ?", media.ID).
|
||||||
Where("purpose = ? OR (purpose = ? AND content_type IN ?)", models.PhotoHighRes, models.MediaOriginal, scanner.WebMimetypes).
|
Where("purpose = ? OR (purpose = ? AND content_type IN ?)", models.PhotoHighRes, models.MediaOriginal, scanner.WebMimetypes).
|
||||||
First(&url).Error
|
First(&url).Error
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ func (r *mediaResolver) HighRes(ctx context.Context, media *models.Media) (*mode
|
||||||
func (r *mediaResolver) Thumbnail(ctx context.Context, media *models.Media) (*models.MediaURL, error) {
|
func (r *mediaResolver) Thumbnail(ctx context.Context, media *models.Media) (*models.MediaURL, error) {
|
||||||
var url models.MediaURL
|
var url models.MediaURL
|
||||||
err := r.Database.
|
err := r.Database.
|
||||||
Where("media_url = ?", media.ID).
|
Where("media_id = ?", media.ID).
|
||||||
Where("purpose = ? OR purpose = ?", models.PhotoThumbnail, models.VideoThumbnail).
|
Where("purpose = ? OR purpose = ?", models.PhotoThumbnail, models.VideoThumbnail).
|
||||||
First(&url).Error
|
First(&url).Error
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ func (r *mediaResolver) VideoWeb(ctx context.Context, media *models.Media) (*mod
|
||||||
|
|
||||||
var url models.MediaURL
|
var url models.MediaURL
|
||||||
err := r.Database.
|
err := r.Database.
|
||||||
Where("media_url = ?", media.ID).
|
Where("media_id = ?", media.ID).
|
||||||
Where("purpose = ?", models.VideoWeb).
|
Where("purpose = ?", models.VideoWeb).
|
||||||
First(&url).Error
|
First(&url).Error
|
||||||
|
|
||||||
|
|
|
@ -20,14 +20,14 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
|
||||||
// Will get from database
|
// Will get from database
|
||||||
var mediaList []models.Media
|
var mediaList []models.Media
|
||||||
|
|
||||||
db.Where("album_id = ?", albumId)
|
query := db.Where("album_id = ?", albumId)
|
||||||
|
|
||||||
// Select media from database that was not found on hard disk
|
// Select media from database that was not found on hard disk
|
||||||
if len(albumMedia) > 0 {
|
if len(albumMedia) > 0 {
|
||||||
db.Not(albumMediaIds)
|
query.Where("NOT id IN ?", albumMediaIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Find(&mediaList).Error; err != nil {
|
if err := query.Find(&mediaList).Error; err != nil {
|
||||||
return []error{errors.Wrap(err, "get media files to be deleted from database")}
|
return []error{errors.Wrap(err, "get media files to be deleted from database")}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +45,11 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(mediaIDs) > 0 {
|
||||||
if err := db.Where("id IN ?", mediaIDs).Delete(models.Media{}).Error; err != nil {
|
if err := db.Where("id IN ?", mediaIDs).Delete(models.Media{}).Error; err != nil {
|
||||||
deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old media from database"))
|
deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old media from database"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return deleteErrors
|
return deleteErrors
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,19 +22,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Higher order function used to check if MediaURL for a given MediaPurpose exists
|
// Higher order function used to check if MediaURL for a given MediaPurpose exists
|
||||||
func makePhotoURLChecker(tx *gorm.DB, mediaID int) (func(purpose models.MediaPurpose) (*models.MediaURL, error), error) {
|
func makePhotoURLChecker(tx *gorm.DB, mediaID int) func(purpose models.MediaPurpose) (*models.MediaURL, error) {
|
||||||
return func(purpose models.MediaPurpose) (*models.MediaURL, error) {
|
return func(purpose models.MediaPurpose) (*models.MediaURL, error) {
|
||||||
var mediaURL models.MediaURL
|
var mediaURL []*models.MediaURL
|
||||||
|
|
||||||
|
result := tx.Where("purpose = ?", purpose).Find(&mediaURL, mediaID)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected > 0 {
|
||||||
|
return mediaURL[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := tx.Where("purpose = ?", purpose).First(&mediaURL, mediaID).Error; err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &mediaURL, nil
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessMedia(tx *gorm.DB, media *models.Media) (bool, error) {
|
func ProcessMedia(tx *gorm.DB, media *models.Media) (bool, error) {
|
||||||
|
@ -68,25 +71,22 @@ func processPhoto(tx *gorm.DB, imageData *EncodeMediaData, photoCachePath *strin
|
||||||
|
|
||||||
didProcess := false
|
didProcess := false
|
||||||
|
|
||||||
photoUrlFromDB, err := makePhotoURLChecker(tx, photo.ID)
|
photoURLFromDB := makePhotoURLChecker(tx, photo.ID)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// original photo url
|
// original photo url
|
||||||
origURL, err := photoUrlFromDB(models.MediaOriginal)
|
origURL, err := photoURLFromDB(models.MediaOriginal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail
|
// Thumbnail
|
||||||
thumbURL, err := photoUrlFromDB(models.PhotoThumbnail)
|
thumbURL, err := photoURLFromDB(models.PhotoThumbnail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "error processing photo thumbnail")
|
return false, errors.Wrap(err, "error processing photo thumbnail")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highres
|
// Highres
|
||||||
highResURL, err := photoUrlFromDB(models.PhotoHighRes)
|
highResURL, err := photoURLFromDB(models.PhotoHighRes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "error processing photo highres")
|
return false, errors.Wrap(err, "error processing photo highres")
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,17 +22,14 @@ func processVideo(tx *gorm.DB, mediaData *EncodeMediaData, videoCachePath *strin
|
||||||
|
|
||||||
log.Printf("Processing video: %s", video.Path)
|
log.Printf("Processing video: %s", video.Path)
|
||||||
|
|
||||||
mediaUrlFromDB, err := makePhotoURLChecker(tx, video.ID)
|
mediaURLFromDB := makePhotoURLChecker(tx, video.ID)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
videoWebURL, err := mediaUrlFromDB(models.VideoWeb)
|
videoWebURL, err := mediaURLFromDB(models.VideoWeb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "error processing video web-format")
|
return false, errors.Wrap(err, "error processing video web-format")
|
||||||
}
|
}
|
||||||
|
|
||||||
videoThumbnailURL, err := mediaUrlFromDB(models.VideoThumbnail)
|
videoThumbnailURL, err := mediaURLFromDB(models.VideoThumbnail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "error processing video thumbnail")
|
return false, errors.Wrap(err, "error processing video thumbnail")
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
|
||||||
for count, photo := range albumPhotos {
|
for count, photo := range albumPhotos {
|
||||||
// tx, err := db.Begin()
|
// tx, err := db.Begin()
|
||||||
|
|
||||||
transactionResult := db.Transaction(func(tx *gorm.DB) error {
|
transactionError := db.Transaction(func(tx *gorm.DB) error {
|
||||||
processing_was_needed, err := ProcessMedia(tx, photo)
|
processing_was_needed, err := ProcessMedia(tx, photo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to process photo (%s)", photo.Path)
|
return errors.Wrapf(err, "failed to process photo (%s)", photo.Path)
|
||||||
|
@ -61,8 +61,8 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *gorm.DB) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if transactionResult.Error != nil {
|
if transactionError != nil {
|
||||||
ScannerError("Failed to begin database transaction: %s", transactionResult.Error)
|
ScannerError("Failed to begin database transaction: %s", transactionError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,14 +52,17 @@ func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *AlbumScannerCa
|
||||||
|
|
||||||
// Check if media already exists
|
// Check if media already exists
|
||||||
{
|
{
|
||||||
var media models.Media
|
var media []*models.Media
|
||||||
if err := tx.Where("path_hash = MD5(?)", mediaPath).First(&media).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
if err == nil {
|
result := tx.Where("path_hash = MD5(?)", mediaPath).Find(&media)
|
||||||
log.Printf("Media already scanned: %s\n", mediaPath)
|
|
||||||
return &media, false, nil
|
if result.Error != nil {
|
||||||
} else {
|
return nil, false, errors.Wrap(result.Error, "scan media fetch from database")
|
||||||
return nil, false, errors.Wrap(err, "scan media fetch from database")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected > 0 {
|
||||||
|
log.Printf("Media already scanned: %s\n", mediaPath)
|
||||||
|
return media[0], false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue