1
Fork 0

Replace database, work on scanning

This commit is contained in:
viktorstrate 2020-11-30 21:29:49 +01:00
parent 364521958b
commit 98f13d76e6
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
9 changed files with 69 additions and 71 deletions

View File

@ -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
} }

View File

@ -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 (

View File

@ -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")

View File

@ -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

View File

@ -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
} }

View File

@ -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")
} }

View File

@ -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")
} }

View File

@ -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)
} }
} }

View File

@ -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
} }
} }