mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 21:19:18 +00:00
Refactoring part 1: Fix some annoying linter warnings in API code (#1064)
* names, constants of reusable strings, removed unneeded `if`, replaced deprecated imports * more deprecated imports replaced * What is this?)) * Fix a path and quote a var in Dockerfile * Addressing review comments --------- Co-authored-by: Konstantin Koval <kkb@ukr.net>
This commit is contained in:
@@ -25,7 +25,7 @@ ENV REACT_APP_BUILD_COMMIT_SHA=${COMMIT_SHA:-}
|
||||
|
||||
WORKDIR /app/ui
|
||||
|
||||
COPY ui/package.json ui/package-lock.json /app/ui
|
||||
COPY ui/package.json ui/package-lock.json /app/ui/
|
||||
RUN npm ci
|
||||
|
||||
COPY ui/ /app/ui
|
||||
@@ -33,7 +33,7 @@ RUN if [ "${BUILD_DATE}" = "undefined" ]; then \
|
||||
export BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ'); \
|
||||
export REACT_APP_BUILD_DATE=${BUILD_DATE}; \
|
||||
fi; \
|
||||
npm run build -- --base=$UI_PUBLIC_URL
|
||||
npm run build -- --base="${UI_PUBLIC_URL}"
|
||||
|
||||
### Build API ###
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23-bookworm AS api
|
||||
|
||||
@@ -191,7 +191,7 @@ func MigrateDatabase(db *gorm.DB) error {
|
||||
|
||||
// v2.3.0 - Changed type of MediaEXIF.Exposure and MediaEXIF.Flash
|
||||
// from string values to decimal and int respectively
|
||||
if err := migrate_exif_fields(db); err != nil {
|
||||
if err := migrateExifFields(db); err != nil {
|
||||
log.Printf("Failed to run exif fields migration: %v\n", err)
|
||||
}
|
||||
|
||||
@@ -206,20 +206,20 @@ func MigrateDatabase(db *gorm.DB) error {
|
||||
func ClearDatabase(db *gorm.DB) error {
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
db_driver := drivers.DatabaseDriverFromEnv()
|
||||
dbDriver := drivers.DatabaseDriverFromEnv()
|
||||
|
||||
if db_driver == drivers.MYSQL {
|
||||
if dbDriver == drivers.MYSQL {
|
||||
if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 0;").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dry_run := tx.Session(&gorm.Session{DryRun: true})
|
||||
dryRun := tx.Session(&gorm.Session{DryRun: true})
|
||||
for _, model := range database_models {
|
||||
// get table name of model structure
|
||||
table := dry_run.Find(model).Statement.Table
|
||||
table := dryRun.Find(model).Statement.Table
|
||||
|
||||
switch db_driver {
|
||||
switch dbDriver {
|
||||
case drivers.POSTGRES:
|
||||
if err := tx.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)).Error; err != nil {
|
||||
return err
|
||||
@@ -236,7 +236,7 @@ func ClearDatabase(db *gorm.DB) error {
|
||||
|
||||
}
|
||||
|
||||
if db_driver == drivers.MYSQL {
|
||||
if dbDriver == drivers.MYSQL {
|
||||
if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 1;").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// Migrate MediaExif fields "exposure" and "flash" from strings to integers
|
||||
func migrate_exif_fields(db *gorm.DB) error {
|
||||
func migrateExifFields(db *gorm.DB) error {
|
||||
mediaExifColumns, err := db.Migrator().ColumnTypes(&models.MediaEXIF{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -26,7 +26,7 @@ func migrate_exif_fields(db *gorm.DB) error {
|
||||
// correct type, do nothing
|
||||
default:
|
||||
// do migration
|
||||
if err := migrate_exif_fields_exposure(db); err != nil {
|
||||
if err := migrateExifFieldsExposure(db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func migrate_exif_fields(db *gorm.DB) error {
|
||||
// correct type, do nothing
|
||||
default:
|
||||
// do migration
|
||||
if err := migrate_exif_fields_flash(db); err != nil {
|
||||
if err := migrateExifFieldsFlash(db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func migrate_exif_fields(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrate_exif_fields_exposure(db *gorm.DB) error {
|
||||
func migrateExifFieldsExposure(db *gorm.DB) error {
|
||||
log.Println("Migrating `media_exif.exposure` from string to double")
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -113,17 +113,17 @@ func migrate_exif_fields_exposure(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrate_exif_fields_flash(db *gorm.DB) error {
|
||||
func migrateExifFieldsFlash(db *gorm.DB) error {
|
||||
log.Println("Migrating `media_exif.flash` from string to int")
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
|
||||
var data_type string
|
||||
if err := tx.Raw("SELECT data_type FROM information_schema.columns WHERE table_name = 'media_exif' AND column_name = 'flash';").Find(&data_type).Error; err != nil {
|
||||
var dataType string
|
||||
if err := tx.Raw("SELECT data_type FROM information_schema.columns WHERE table_name = 'media_exif' AND column_name = 'flash';").Find(&dataType).Error; err != nil {
|
||||
return errors.Wrapf(err, "read data_type of column media_exif.flash")
|
||||
}
|
||||
|
||||
if data_type == "bigint" {
|
||||
if dataType == "bigint" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ func Album(db *gorm.DB, user *models.User, id int) (*models.Album, error) {
|
||||
}
|
||||
|
||||
func AlbumPath(db *gorm.DB, user *models.User, album *models.Album) ([]*models.Album, error) {
|
||||
var album_path []*models.Album
|
||||
var albumPath []*models.Album
|
||||
|
||||
err := db.Raw(`
|
||||
WITH recursive path_albums AS (
|
||||
@@ -99,11 +99,11 @@ func AlbumPath(db *gorm.DB, user *models.User, album *models.Album) ([]*models.A
|
||||
SELECT parent.* FROM path_albums child JOIN albums parent ON parent.id = child.parent_album_id
|
||||
)
|
||||
SELECT * FROM path_albums WHERE id != ?
|
||||
`, album.ID, album.ID).Scan(&album_path).Error
|
||||
`, album.ID, album.ID).Scan(&albumPath).Error
|
||||
|
||||
// Make sure to only return albums this user owns
|
||||
for i := len(album_path) - 1; i >= 0; i-- {
|
||||
album := album_path[i]
|
||||
for i := len(albumPath) - 1; i >= 0; i-- {
|
||||
album := albumPath[i]
|
||||
|
||||
owns, err := user.OwnsAlbum(db, album)
|
||||
if err != nil {
|
||||
@@ -111,7 +111,7 @@ func AlbumPath(db *gorm.DB, user *models.User, album *models.Album) ([]*models.A
|
||||
}
|
||||
|
||||
if !owns {
|
||||
album_path = album_path[i+1:]
|
||||
albumPath = albumPath[i+1:]
|
||||
break
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func AlbumPath(db *gorm.DB, user *models.User, album *models.Album) ([]*models.A
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return album_path, nil
|
||||
return albumPath, nil
|
||||
}
|
||||
|
||||
func SetAlbumCover(db *gorm.DB, user *models.User, mediaID int) (*models.Album, error) {
|
||||
|
||||
@@ -140,8 +140,8 @@ func TestAlbumCover(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
user_pass := "password"
|
||||
regularUser, err := models.RegisterUser(db, "user1", &user_pass, false)
|
||||
userPass := "password"
|
||||
regularUser, err := models.RegisterUser(db, "user1", &userPass, false)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func Search(db *gorm.DB, query string, userID int, _limitMedia *int, _limitAlbums *int) (*models.SearchResult, error) {
|
||||
limitMedia := 10
|
||||
limitAlbums := 10
|
||||
func Search(db *gorm.DB, query string, userID int, limitMedia *int, limitAlbums *int) (*models.SearchResult, error) {
|
||||
limitMediaInternal := 10
|
||||
limitAlbumsInternal := 10
|
||||
|
||||
if _limitMedia != nil {
|
||||
limitMedia = *_limitMedia
|
||||
if limitMedia != nil {
|
||||
limitMediaInternal = *limitMedia
|
||||
}
|
||||
|
||||
if _limitAlbums != nil {
|
||||
limitAlbums = *_limitAlbums
|
||||
if limitAlbums != nil {
|
||||
limitAlbumsInternal = *limitAlbums
|
||||
}
|
||||
|
||||
wildQuery := "%" + strings.ToLower(query) + "%"
|
||||
@@ -42,7 +42,7 @@ func Search(db *gorm.DB, query string, userID int, _limitMedia *int, _limitAlbum
|
||||
Vars: []interface{}{wildQuery, wildQuery},
|
||||
WithoutParentheses: true},
|
||||
}).
|
||||
Limit(limitMedia).Find(&media).Error
|
||||
Limit(limitMediaInternal).Find(&media).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "searching media")
|
||||
@@ -59,7 +59,7 @@ func Search(db *gorm.DB, query string, userID int, _limitMedia *int, _limitAlbum
|
||||
Vars: []interface{}{wildQuery, wildQuery},
|
||||
WithoutParentheses: true},
|
||||
}).
|
||||
Limit(limitAlbums).
|
||||
Limit(limitAlbumsInternal).
|
||||
Find(&albums).Error
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func MyTimeline(db *gorm.DB, user *models.User, paginate *models.Pagination, onlyFavorites *bool, fromDate *time.Time) ([]*models.Media, error) {
|
||||
const albumsTitleASC = "albums.title ASC"
|
||||
|
||||
query := db.
|
||||
Joins("JOIN albums ON media.album_id = albums.id").
|
||||
@@ -20,19 +21,19 @@ func MyTimeline(db *gorm.DB, user *models.User, paginate *models.Pagination, onl
|
||||
Order("DATE_TRUNC('year', date_shot) DESC").
|
||||
Order("DATE_TRUNC('month', date_shot) DESC").
|
||||
Order("DATE_TRUNC('day', date_shot) DESC").
|
||||
Order("albums.title ASC").
|
||||
Order(albumsTitleASC).
|
||||
Order("media.date_shot DESC")
|
||||
case drivers.SQLITE:
|
||||
query = query.
|
||||
Order("strftime('%Y-%m-%d', media.date_shot) DESC"). // convert to YYYY-MM-DD
|
||||
Order("albums.title ASC").
|
||||
Order(albumsTitleASC).
|
||||
Order("TIME(media.date_shot) DESC")
|
||||
default:
|
||||
query = query.
|
||||
Order("YEAR(media.date_shot) DESC").
|
||||
Order("MONTH(media.date_shot) DESC").
|
||||
Order("DAY(media.date_shot) DESC").
|
||||
Order("albums.title ASC").
|
||||
Order(albumsTitleASC).
|
||||
Order("TIME(media.date_shot) DESC")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,14 @@ import (
|
||||
)
|
||||
|
||||
func TestAlbumGetChildrenAndParents(t *testing.T) {
|
||||
const photosPath = "/photos"
|
||||
const photosChild1Path = "/photos/child1"
|
||||
const photosChild1SubchildPath = "/photos/child1/subchild"
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
rootAlbum := models.Album{
|
||||
Title: "root",
|
||||
Path: "/photos",
|
||||
Path: photosPath,
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&rootAlbum).Error) {
|
||||
@@ -23,7 +26,7 @@ func TestAlbumGetChildrenAndParents(t *testing.T) {
|
||||
children := []models.Album{
|
||||
{
|
||||
Title: "child1",
|
||||
Path: "/photos/child1",
|
||||
Path: photosChild1Path,
|
||||
ParentAlbumID: &rootAlbum.ID,
|
||||
},
|
||||
{
|
||||
@@ -41,47 +44,47 @@ func TestAlbumGetChildrenAndParents(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
sub_child := models.Album{
|
||||
subChild := models.Album{
|
||||
Title: "subchild",
|
||||
Path: "/photos/child1/subchild",
|
||||
Path: photosChild1SubchildPath,
|
||||
ParentAlbumID: &children[0].ID,
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&sub_child).Error) {
|
||||
if !assert.NoError(t, db.Save(&subChild).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
verifyResult := func(t *testing.T, expected_albums []*models.Album, result []*models.Album) {
|
||||
assert.Equal(t, len(expected_albums), len(result))
|
||||
verifyResult := func(t *testing.T, expectedAlbums []*models.Album, result []*models.Album) {
|
||||
assert.Equal(t, len(expectedAlbums), len(result))
|
||||
|
||||
for _, expected := range expected_albums {
|
||||
found_expected := false
|
||||
for _, expected := range expectedAlbums {
|
||||
foundExpected := false
|
||||
for _, item := range result {
|
||||
if item.Title == expected.Title && item.Path == expected.Path {
|
||||
found_expected = true
|
||||
foundExpected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found_expected {
|
||||
if !foundExpected {
|
||||
assert.Failf(t, "albums did not match", "expected to find item: %v", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Album get children", func(t *testing.T) {
|
||||
root_children, err := rootAlbum.GetChildren(db, nil)
|
||||
rootChildren, err := rootAlbum.GetChildren(db, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
expected_children := []*models.Album{
|
||||
expectedChildren := []*models.Album{
|
||||
{
|
||||
Title: "root",
|
||||
Path: "/photos",
|
||||
Path: photosPath,
|
||||
},
|
||||
{
|
||||
Title: "child1",
|
||||
Path: "/photos/child1",
|
||||
Path: photosChild1Path,
|
||||
},
|
||||
{
|
||||
Title: "child2",
|
||||
@@ -89,35 +92,35 @@ func TestAlbumGetChildrenAndParents(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Title: "subchild",
|
||||
Path: "/photos/child1/subchild",
|
||||
Path: photosChild1SubchildPath,
|
||||
},
|
||||
}
|
||||
|
||||
verifyResult(t, expected_children, root_children)
|
||||
verifyResult(t, expectedChildren, rootChildren)
|
||||
})
|
||||
|
||||
t.Run("Album get parents", func(t *testing.T) {
|
||||
parents, err := sub_child.GetParents(db, nil)
|
||||
parents, err := subChild.GetParents(db, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
expected_parents := []*models.Album{
|
||||
expectedParents := []*models.Album{
|
||||
{
|
||||
Title: "root",
|
||||
Path: "/photos",
|
||||
Path: photosPath,
|
||||
},
|
||||
{
|
||||
Title: "child1",
|
||||
Path: "/photos/child1",
|
||||
Path: photosChild1Path,
|
||||
},
|
||||
{
|
||||
Title: "subchild",
|
||||
Path: "/photos/child1/subchild",
|
||||
Path: photosChild1SubchildPath,
|
||||
},
|
||||
}
|
||||
|
||||
verifyResult(t, expected_parents, parents)
|
||||
verifyResult(t, expectedParents, parents)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func (fr *FaceRectangle) Scan(value interface{}) error {
|
||||
slices := strings.Split(stringArray, ":")
|
||||
|
||||
if len(slices) != 4 {
|
||||
return fmt.Errorf("Invalid face rectangle format, expected 4 values, got %d", len(slices))
|
||||
return fmt.Errorf("invalid face rectangle format, expected 4 values, got %d", len(slices))
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const mimeJpeg = "image/jpeg"
|
||||
|
||||
func TestSanitizeMediaName(t *testing.T) {
|
||||
tests := [][2]string{
|
||||
{"filename.png", "filename_png"},
|
||||
@@ -53,7 +55,7 @@ func TestMediaURLCachePath(t *testing.T) {
|
||||
func TestMediaURLGetURL(t *testing.T) {
|
||||
photo := models.MediaURL{
|
||||
MediaName: "photo.jpg",
|
||||
ContentType: "image/jpeg",
|
||||
ContentType: mimeJpeg,
|
||||
Purpose: models.PhotoHighRes,
|
||||
}
|
||||
|
||||
@@ -76,12 +78,12 @@ func TestMediaGetThumbnail(t *testing.T) {
|
||||
MediaURL: []models.MediaURL{
|
||||
{
|
||||
MediaName: "photo.jpg",
|
||||
ContentType: "image/jpeg",
|
||||
ContentType: mimeJpeg,
|
||||
Purpose: models.PhotoHighRes,
|
||||
},
|
||||
{
|
||||
MediaName: "thumbnail.jpg",
|
||||
ContentType: "image/jpeg",
|
||||
ContentType: mimeJpeg,
|
||||
Purpose: models.PhotoThumbnail,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -54,16 +54,16 @@ func (u *UserPreferences) BeforeSave(tx *gorm.DB) error {
|
||||
}
|
||||
|
||||
if u.Language != nil {
|
||||
lang_str := string(*u.Language)
|
||||
found_match := false
|
||||
langStr := string(*u.Language)
|
||||
foundMatch := false
|
||||
for _, lang := range AllLanguageTranslation {
|
||||
if string(lang) == lang_str {
|
||||
found_match = true
|
||||
if string(lang) == langStr {
|
||||
foundMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found_match {
|
||||
if !foundMatch {
|
||||
return errors.New("invalid language value")
|
||||
}
|
||||
}
|
||||
@@ -133,12 +133,12 @@ func (user *User) GenerateAccessToken(db *gorm.DB) (*AccessToken, error) {
|
||||
bytes[i] = CHARACTERS[b%byte(len(CHARACTERS))]
|
||||
}
|
||||
|
||||
token_value := string(bytes)
|
||||
tokenValue := string(bytes)
|
||||
expire := time.Now().Add(14 * 24 * time.Hour)
|
||||
|
||||
token := AccessToken{
|
||||
UserID: user.ID,
|
||||
Value: token_value,
|
||||
Value: tokenValue,
|
||||
Expire: expire,
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ func TestUserOwnsAlbum(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
sub_albums := []models.Album{
|
||||
subAlbums := []models.Album{
|
||||
{
|
||||
Title: "subalbum1",
|
||||
Path: "/photos/album2/subalbum1",
|
||||
@@ -155,7 +155,7 @@ func TestUserOwnsAlbum(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Model(&user).Association("Albums").Append(&sub_albums)) {
|
||||
if !assert.NoError(t, db.Model(&user).Association("Albums").Append(&subAlbums)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -165,22 +165,22 @@ func TestUserOwnsAlbum(t *testing.T) {
|
||||
assert.True(t, owns)
|
||||
}
|
||||
|
||||
for _, album := range sub_albums {
|
||||
for _, album := range subAlbums {
|
||||
owns, err := user.OwnsAlbum(db, &album)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, owns)
|
||||
}
|
||||
|
||||
separate_album := models.Album{
|
||||
separateAlbum := models.Album{
|
||||
Title: "separate_album",
|
||||
Path: "/my_media/album123",
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&separate_album).Error) {
|
||||
if !assert.NoError(t, db.Save(&separateAlbum).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
owns, err := user.OwnsAlbum(db, &separate_album)
|
||||
owns, err := user.OwnsAlbum(db, &separateAlbum)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, owns)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const faceGroupIDisQuestion = "face_group_id = ?"
|
||||
const mediaAlbumIDinQuestion = "media.album_id IN (?)"
|
||||
const imageFacesIDinQuestion = "image_faces.id IN (?)"
|
||||
|
||||
var ErrFaceDetectorNotInitialized = errors.New("face detector not initialized")
|
||||
|
||||
type imageFaceResolver struct {
|
||||
*Resolver
|
||||
}
|
||||
@@ -33,7 +39,7 @@ func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace)
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
var faceGroup models.FaceGroup
|
||||
@@ -62,7 +68,7 @@ func (r faceGroupResolver) ImageFaces(ctx context.Context, obj *models.FaceGroup
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
if err := user.FillAlbums(db); err != nil {
|
||||
@@ -76,7 +82,7 @@ func (r faceGroupResolver) ImageFaces(ctx context.Context, obj *models.FaceGroup
|
||||
|
||||
query := db.
|
||||
Joins("Media").
|
||||
Where("face_group_id = ?", obj.ID).
|
||||
Where(faceGroupIDisQuestion, obj.ID).
|
||||
Where("album_id IN (?)", userAlbumIDs)
|
||||
|
||||
query = models.FormatSQL(query, nil, paginate)
|
||||
@@ -97,7 +103,7 @@ func (r faceGroupResolver) ImageFaceCount(ctx context.Context, obj *models.FaceG
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return -1, errors.New("face detector not initialized")
|
||||
return -1, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
if err := user.FillAlbums(db); err != nil {
|
||||
@@ -112,7 +118,7 @@ func (r faceGroupResolver) ImageFaceCount(ctx context.Context, obj *models.FaceG
|
||||
query := db.
|
||||
Model(&models.ImageFace{}).
|
||||
Joins("Media").
|
||||
Where("face_group_id = ?", obj.ID).
|
||||
Where(faceGroupIDisQuestion, obj.ID).
|
||||
Where("album_id IN (?)", userAlbumIDs)
|
||||
|
||||
var count int64
|
||||
@@ -131,7 +137,7 @@ func (r *queryResolver) FaceGroup(ctx context.Context, id int) (*models.FaceGrou
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
if err := user.FillAlbums(db); err != nil {
|
||||
@@ -147,7 +153,7 @@ func (r *queryResolver) FaceGroup(ctx context.Context, id int) (*models.FaceGrou
|
||||
Joins("LEFT JOIN image_faces ON image_faces.face_group_id = face_groups.id").
|
||||
Joins("LEFT JOIN media ON image_faces.media_id = media.id").
|
||||
Where("face_groups.id = ?", id).
|
||||
Where("media.album_id IN (?)", userAlbumIDs)
|
||||
Where(mediaAlbumIDinQuestion, userAlbumIDs)
|
||||
|
||||
var faceGroup models.FaceGroup
|
||||
if err := faceGroupQuery.Find(&faceGroup).Error; err != nil {
|
||||
@@ -165,7 +171,7 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
if err := user.FillAlbums(db); err != nil {
|
||||
@@ -179,7 +185,7 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin
|
||||
|
||||
faceGroupQuery := db.
|
||||
Joins("JOIN image_faces ON image_faces.face_group_id = face_groups.id").
|
||||
Where("image_faces.media_id IN (?)", db.Select("media.id").Table("media").Where("media.album_id IN (?)", userAlbumIDs)).
|
||||
Where("image_faces.media_id IN (?)", db.Select("media.id").Table("media").Where(mediaAlbumIDinQuestion, userAlbumIDs)).
|
||||
Group("image_faces.face_group_id").
|
||||
Group("face_groups.id").
|
||||
Order("CASE WHEN label IS NULL THEN 1 ELSE 0 END").
|
||||
@@ -203,7 +209,7 @@ func (r *mutationResolver) SetFaceGroupLabel(ctx context.Context, faceGroupID in
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
faceGroup, err := userOwnedFaceGroup(db, user, faceGroupID)
|
||||
@@ -226,7 +232,7 @@ func (r *mutationResolver) CombineFaceGroups(ctx context.Context, destinationFac
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
destinationFaceGroup, err := userOwnedFaceGroup(db, user, destinationFaceGroupID)
|
||||
@@ -240,7 +246,7 @@ func (r *mutationResolver) CombineFaceGroups(ctx context.Context, destinationFac
|
||||
}
|
||||
|
||||
updateError := db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&models.ImageFace{}).Where("face_group_id = ?", sourceFaceGroup.ID).Update("face_group_id", destinationFaceGroup.ID).Error; err != nil {
|
||||
if err := tx.Model(&models.ImageFace{}).Where(faceGroupIDisQuestion, sourceFaceGroup.ID).Update("face_group_id", destinationFaceGroup.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -268,7 +274,7 @@ func (r *mutationResolver) MoveImageFaces(ctx context.Context, imageFaceIDs []in
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
userOwnedImageFaceIDs := make([]int, 0)
|
||||
@@ -294,7 +300,7 @@ func (r *mutationResolver) MoveImageFaces(ctx context.Context, imageFaceIDs []in
|
||||
var sourceFaceGroups []*models.FaceGroup
|
||||
if err := tx.
|
||||
Joins("LEFT JOIN image_faces ON image_faces.face_group_id = face_groups.id").
|
||||
Where("image_faces.id IN (?)", userOwnedImageFaceIDs).
|
||||
Where(imageFacesIDinQuestion, userOwnedImageFaceIDs).
|
||||
Find(&sourceFaceGroups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -309,7 +315,7 @@ func (r *mutationResolver) MoveImageFaces(ctx context.Context, imageFaceIDs []in
|
||||
// delete face groups if they have become empty
|
||||
for _, faceGroup := range sourceFaceGroups {
|
||||
var count int64
|
||||
if err := tx.Model(&models.ImageFace{}).Where("face_group_id = ?", faceGroup.ID).Count(&count).Error; err != nil {
|
||||
if err := tx.Model(&models.ImageFace{}).Where(faceGroupIDisQuestion, faceGroup.ID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -340,7 +346,7 @@ func (r *mutationResolver) RecognizeUnlabeledFaces(ctx context.Context) ([]*mode
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
var updatedImageFaces []*models.ImageFace
|
||||
@@ -367,7 +373,7 @@ func (r *mutationResolver) DetachImageFaces(ctx context.Context, imageFaceIDs []
|
||||
}
|
||||
|
||||
if face_detection.GlobalFaceDetector == nil {
|
||||
return nil, errors.New("face detector not initialized")
|
||||
return nil, ErrFaceDetectorNotInitialized
|
||||
}
|
||||
|
||||
userOwnedImageFaceIDs := make([]int, 0)
|
||||
@@ -431,13 +437,13 @@ func userOwnedFaceGroup(db *gorm.DB, user *models.User, faceGroupID int) (*model
|
||||
Select("image_faces.id").
|
||||
Table("image_faces").
|
||||
Joins("JOIN media ON media.id = image_faces.media_id").
|
||||
Where("media.album_id IN (?)", userAlbumIDs)
|
||||
Where(mediaAlbumIDinQuestion, userAlbumIDs)
|
||||
|
||||
faceGroupQuery := db.
|
||||
Model(&models.FaceGroup{}).
|
||||
Joins("JOIN image_faces ON face_groups.id = image_faces.face_group_id").
|
||||
Where("face_groups.id = ?", faceGroupID).
|
||||
Where("image_faces.id IN (?)", imageFaceQuery)
|
||||
Where(imageFacesIDinQuestion, imageFaceQuery)
|
||||
|
||||
var faceGroup models.FaceGroup
|
||||
if err := faceGroupQuery.Find(&faceGroup).Error; err != nil {
|
||||
@@ -463,8 +469,8 @@ func getUserOwnedImageFaces(tx *gorm.DB, user *models.User, imageFaceIDs []int)
|
||||
var userOwnedImageFaces []*models.ImageFace
|
||||
if err := tx.
|
||||
Joins("JOIN media ON media.id = image_faces.media_id").
|
||||
Where("media.album_id IN (?)", userAlbumIDs).
|
||||
Where("image_faces.id IN (?)", imageFaceIDs).
|
||||
Where(mediaAlbumIDinQuestion, userAlbumIDs).
|
||||
Where(imageFacesIDinQuestion, imageFaceIDs).
|
||||
Find(&userOwnedImageFaces).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,18 +11,20 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const internalServerError = "internal server error"
|
||||
|
||||
func authenticateMedia(media *models.Media, db *gorm.DB, r *http.Request) (success bool, responseMessage string, responseStatus int, errorMessage error) {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
|
||||
if user != nil {
|
||||
var album models.Album
|
||||
if err := db.First(&album, media.AlbumID).Error; err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
return false, internalServerError, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
ownsAlbum, err := user.OwnsAlbum(db, &album)
|
||||
if err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
return false, internalServerError, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if !ownsAlbum {
|
||||
@@ -44,7 +46,7 @@ func authenticateAlbum(album *models.Album, db *gorm.DB, r *http.Request) (succe
|
||||
if user != nil {
|
||||
ownsAlbum, err := user.OwnsAlbum(db, album)
|
||||
if err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
return false, internalServerError, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if !ownsAlbum {
|
||||
@@ -70,7 +72,7 @@ func shareTokenFromRequest(db *gorm.DB, r *http.Request, mediaID *int, albumID *
|
||||
var shareToken models.ShareToken
|
||||
|
||||
if err := db.Where("value = ?", token).First(&shareToken).Error; err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
return false, internalServerError, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Validate share token password, if set
|
||||
@@ -86,7 +88,7 @@ func shareTokenFromRequest(db *gorm.DB, r *http.Request, mediaID *int, albumID *
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return false, "unauthorized", http.StatusForbidden, errors.New("incorrect password for share token")
|
||||
} else {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
return false, internalServerError, http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +115,7 @@ func shareTokenFromRequest(db *gorm.DB, r *http.Request, mediaID *int, albumID *
|
||||
`, *shareToken.AlbumID, albumID).Find(&count).Error
|
||||
|
||||
if err != nil {
|
||||
return false, "internal server error", http.StatusInternalServerError, err
|
||||
return false, internalServerError, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
|
||||
@@ -16,6 +16,9 @@ import (
|
||||
)
|
||||
|
||||
func TestAuthenticateRoute(t *testing.T) {
|
||||
const imageData = "IMAGE DATA"
|
||||
const albumData = "ALBUM DATA"
|
||||
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
user, err := models.RegisterUser(db, "username", nil, false)
|
||||
@@ -44,7 +47,7 @@ func TestAuthenticateRoute(t *testing.T) {
|
||||
|
||||
t.Run("Authenticate Media", func(t *testing.T) {
|
||||
t.Run("Authorized request", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/photo/image.jpg", strings.NewReader("IMAGE DATA"))
|
||||
req := httptest.NewRequest("GET", "/photo/image.jpg", strings.NewReader(imageData))
|
||||
ctx := auth.AddUserToContext(req.Context(), user)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
@@ -57,7 +60,7 @@ func TestAuthenticateRoute(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Request without access token", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/photo/image.jpg", strings.NewReader("IMAGE DATA"))
|
||||
req := httptest.NewRequest("GET", "/photo/image.jpg", strings.NewReader(imageData))
|
||||
|
||||
success, responseMessage, responseStatus, err := authenticateMedia(&media, db, req)
|
||||
|
||||
@@ -76,7 +79,7 @@ func TestAuthenticateRoute(t *testing.T) {
|
||||
|
||||
t.Run("Request with share token", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/photo/image.jpg?token=%s", shareToken.Value)
|
||||
req := httptest.NewRequest("GET", url, strings.NewReader("IMAGE DATA"))
|
||||
req := httptest.NewRequest("GET", url, strings.NewReader(imageData))
|
||||
|
||||
cookie := http.Cookie{
|
||||
Name: fmt.Sprintf("share-token-pw-%s", shareToken.Value),
|
||||
@@ -95,7 +98,7 @@ func TestAuthenticateRoute(t *testing.T) {
|
||||
|
||||
t.Run("Authenticate Album", func(t *testing.T) {
|
||||
t.Run("Authorized request", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/download/album/1", strings.NewReader("ALBUM DATA"))
|
||||
req := httptest.NewRequest("GET", "/download/album/1", strings.NewReader(albumData))
|
||||
ctx := auth.AddUserToContext(req.Context(), user)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
@@ -108,7 +111,7 @@ func TestAuthenticateRoute(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Request without access token", func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/download/album/1", strings.NewReader("ALBUM DATA"))
|
||||
req := httptest.NewRequest("GET", "/download/album/1", strings.NewReader(albumData))
|
||||
|
||||
success, responseMessage, responseStatus, err := authenticateAlbum(&album, db, req)
|
||||
|
||||
@@ -127,7 +130,7 @@ func TestAuthenticateRoute(t *testing.T) {
|
||||
|
||||
t.Run("Request with share token", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/download/album/1?token=%s", shareToken.Value)
|
||||
req := httptest.NewRequest("GET", url, strings.NewReader("ALBUM DATA"))
|
||||
req := httptest.NewRequest("GET", url, strings.NewReader(albumData))
|
||||
|
||||
cookie := http.Cookie{
|
||||
Name: fmt.Sprintf("share-token-pw-%s", shareToken.Value),
|
||||
|
||||
@@ -47,7 +47,7 @@ func RegisterDownloadRoutes(db *gorm.DB, router *mux.Router) {
|
||||
var mediaURLs []*models.MediaURL
|
||||
if err := db.Joins("Media").Where(mediaWhereQuery, album.ID).Where("media_urls.purpose IN (?)", mediaPurposeList).Find(&mediaURLs).Error; err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func RegisterDownloadRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to create a file in zip, when downloading album (%d): %v\n", album.ID, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func RegisterDownloadRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to get mediaURL cache path, when downloading album (%d): %v\n", album.ID, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func RegisterDownloadRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to open file to include in zip, when downloading album (%d): %v\n", album.ID, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -91,14 +91,14 @@ func RegisterDownloadRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to copy file data, when downloading album (%d): %v\n", album.ID, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
if err := fileData.Close(); err != nil {
|
||||
log.Printf("ERROR: Failed to close file, when downloading album (%d): %v\n", album.ID, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func RegisterPhotoRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,14 +49,14 @@ func RegisterPhotoRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err = scanner.ProcessSingleMedia(db, media); err != nil {
|
||||
log.Printf("ERROR: processing image not found in cache (%s): %s\n", cachedPath, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = os.Stat(cachedPath); err != nil {
|
||||
log.Printf("ERROR: after reprocessing image not found in cache (%s): %s\n", cachedPath, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func RegisterVideoRoutes(db *gorm.DB, router *mux.Router) {
|
||||
} else {
|
||||
log.Printf("ERROR: Can not handle media_purpose for video: %s\n", mediaURL.Purpose)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@ func RegisterVideoRoutes(db *gorm.DB, router *mux.Router) {
|
||||
if err := scanner.ProcessSingleMedia(db, media); err != nil {
|
||||
log.Printf("ERROR: processing video not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := os.Stat(cachedPath); err != nil {
|
||||
log.Printf("ERROR: after reprocessing video not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("internal server error"))
|
||||
w.Write([]byte(internalServerError))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func sanitizeEXIF(exif *models.MediaEXIF) {
|
||||
}
|
||||
}
|
||||
|
||||
func extractValidGpsData(fileInfo *exiftool.FileMetadata, media_path string) (*float64, *float64) {
|
||||
func extractValidGpsData(fileInfo *exiftool.FileMetadata, mediaPath string) (*float64, *float64) {
|
||||
var GPSLat, GPSLong *float64
|
||||
|
||||
// GPS coordinates - longitude
|
||||
@@ -82,13 +82,13 @@ func extractValidGpsData(fileInfo *exiftool.FileMetadata, media_path string) (*f
|
||||
if (GPSLat != nil && math.Abs(*GPSLat) > 90) || (GPSLong != nil && math.Abs(*GPSLong) > 90) {
|
||||
log.Printf(
|
||||
"Incorrect GPS data in the %s Exif data: %f, %f, while expected values between '-90' and '90'. Ignoring GPS data.",
|
||||
media_path, *GPSLat, *GPSLong)
|
||||
mediaPath, *GPSLat, *GPSLong)
|
||||
return nil, nil
|
||||
}
|
||||
return GPSLat, GPSLong
|
||||
}
|
||||
|
||||
func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
func (p *externalExifParser) ParseExif(mediaPath string) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
// ExifTool - No print conversion mode
|
||||
if p.et == nil {
|
||||
et, err := exiftool.NewExiftool(exiftool.NoPrintConversion())
|
||||
@@ -100,39 +100,39 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo, err := p.dataLoader.Load(media_path)
|
||||
fileInfo, err := p.dataLoader.Load(mediaPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newExif := models.MediaEXIF{}
|
||||
found_exif := false
|
||||
foundExif := false
|
||||
|
||||
// Get description
|
||||
description, err := fileInfo.GetString("ImageDescription")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Description = &description
|
||||
}
|
||||
|
||||
// Get camera model
|
||||
model, err := fileInfo.GetString("Model")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Camera = &model
|
||||
}
|
||||
|
||||
// Get Camera make
|
||||
make, err := fileInfo.GetString("Make")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Maker = &make
|
||||
}
|
||||
|
||||
// Get lens
|
||||
lens, err := fileInfo.GetString("LensModel")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Lens = &lens
|
||||
}
|
||||
|
||||
@@ -144,13 +144,13 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me
|
||||
layout := "2006:01:02 15:04:05"
|
||||
dateTime, err := time.Parse(layout, date)
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.DateShot = &dateTime
|
||||
} else {
|
||||
layoutWithOffset := "2006:01:02 15:04:05-07:00"
|
||||
dateTime, err = time.Parse(layoutWithOffset, date)
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.DateShot = &dateTime
|
||||
}
|
||||
}
|
||||
@@ -161,59 +161,59 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me
|
||||
// Get exposure time
|
||||
exposureTime, err := fileInfo.GetFloat("ExposureTime")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Exposure = &exposureTime
|
||||
}
|
||||
|
||||
// Get aperture
|
||||
aperture, err := fileInfo.GetFloat("Aperture")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Aperture = &aperture
|
||||
}
|
||||
|
||||
// Get ISO
|
||||
iso, err := fileInfo.GetInt("ISO")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Iso = &iso
|
||||
}
|
||||
|
||||
// Get focal length
|
||||
focalLen, err := fileInfo.GetFloat("FocalLength")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.FocalLength = &focalLen
|
||||
}
|
||||
|
||||
// Get flash info
|
||||
flash, err := fileInfo.GetInt("Flash")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Flash = &flash
|
||||
}
|
||||
|
||||
// Get orientation
|
||||
orientation, err := fileInfo.GetInt("Orientation")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.Orientation = &orientation
|
||||
}
|
||||
|
||||
// Get exposure program
|
||||
expProgram, err := fileInfo.GetInt("ExposureProgram")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
newExif.ExposureProgram = &expProgram
|
||||
}
|
||||
|
||||
// Get GPS data
|
||||
newExif.GPSLatitude, newExif.GPSLongitude = extractValidGpsData(&fileInfo, media_path)
|
||||
newExif.GPSLatitude, newExif.GPSLongitude = extractValidGpsData(&fileInfo, mediaPath)
|
||||
if (newExif.GPSLatitude != nil) && (newExif.GPSLongitude != nil) {
|
||||
found_exif = true
|
||||
foundExif = true
|
||||
}
|
||||
|
||||
if !found_exif {
|
||||
if !foundExif {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,17 @@ import (
|
||||
// internalExifParser is an exif parser that parses the media without the use of external tools
|
||||
type internalExifParser struct{}
|
||||
|
||||
const couldNotReadXfromEXIFy = "could not read %s from EXIF: %s"
|
||||
const warnEXIFtagXreturnedNully = "WARN: EXIF tag %s returned null: %s\n"
|
||||
|
||||
var ErrNullExifTag = errors.New("exif tag returned null")
|
||||
|
||||
func NewInternalExifParser() ExifParser {
|
||||
return internalExifParser{}
|
||||
}
|
||||
|
||||
func (p internalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
photoFile, err := os.Open(media_path)
|
||||
func (p internalExifParser) ParseExif(mediaPath string) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
photoFile, err := os.Open(mediaPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -45,22 +50,22 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
|
||||
newExif := models.MediaEXIF{}
|
||||
|
||||
description, err := p.readStringTag(exifTags, exif.ImageDescription, media_path)
|
||||
description, err := p.readStringTag(exifTags, exif.ImageDescription, mediaPath)
|
||||
if err == nil {
|
||||
newExif.Description = description
|
||||
}
|
||||
|
||||
model, err := p.readStringTag(exifTags, exif.Model, media_path)
|
||||
model, err := p.readStringTag(exifTags, exif.Model, mediaPath)
|
||||
if err == nil {
|
||||
newExif.Camera = model
|
||||
}
|
||||
|
||||
maker, err := p.readStringTag(exifTags, exif.Make, media_path)
|
||||
maker, err := p.readStringTag(exifTags, exif.Make, mediaPath)
|
||||
if err == nil {
|
||||
newExif.Maker = maker
|
||||
}
|
||||
|
||||
lens, err := p.readStringTag(exifTags, exif.LensModel, media_path)
|
||||
lens, err := p.readStringTag(exifTags, exif.LensModel, mediaPath)
|
||||
if err == nil {
|
||||
newExif.Lens = lens
|
||||
}
|
||||
@@ -68,8 +73,8 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
date, err := exifTags.DateTime()
|
||||
if err == nil {
|
||||
_, tz := date.Zone()
|
||||
date_utc := date.Add(time.Duration(tz) * time.Second).UTC()
|
||||
newExif.DateShot = &date_utc
|
||||
dateUTC := date.Add(time.Duration(tz) * time.Second).UTC()
|
||||
newExif.DateShot = &dateUTC
|
||||
}
|
||||
|
||||
exposureTag, err := exifTags.Get(exif.ExposureTime)
|
||||
@@ -81,7 +86,7 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
}
|
||||
}
|
||||
|
||||
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media_path)
|
||||
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, mediaPath)
|
||||
if err == nil {
|
||||
aperture, _ := apertureRat.Float64()
|
||||
newExif.Aperture = &aperture
|
||||
@@ -89,11 +94,11 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
|
||||
isoTag, err := exifTags.Get(exif.ISOSpeedRatings)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %v\n", media_path)
|
||||
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %v\n", mediaPath)
|
||||
} else {
|
||||
iso, err := isoTag.Int(0)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %v\n", media_path)
|
||||
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %v\n", mediaPath)
|
||||
} else {
|
||||
iso64 := int64(iso)
|
||||
newExif.Iso = &iso64
|
||||
@@ -106,36 +111,32 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
if err == nil {
|
||||
focalLength, _ := focalLengthRat.Float64()
|
||||
newExif.FocalLength = &focalLength
|
||||
|
||||
} else {
|
||||
// For some photos, the focal length cannot be read as a rational value,
|
||||
// but is instead the second value read as an integer
|
||||
|
||||
if err == nil {
|
||||
focalLength, err := focalLengthTag.Int(1)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %v\n%s\n", media_path, err)
|
||||
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %v\n%s\n", mediaPath, err)
|
||||
} else {
|
||||
focalLenFloat := float64(focalLength)
|
||||
newExif.FocalLength = &focalLenFloat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flash, err := p.readIntegerTag(exifTags, exif.Flash, media_path)
|
||||
flash, err := p.readIntegerTag(exifTags, exif.Flash, mediaPath)
|
||||
if err == nil {
|
||||
flash64 := int64(*flash)
|
||||
newExif.Flash = &flash64
|
||||
}
|
||||
|
||||
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media_path)
|
||||
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, mediaPath)
|
||||
if err == nil {
|
||||
orientation64 := int64(*orientation)
|
||||
newExif.Orientation = &orientation64
|
||||
}
|
||||
|
||||
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media_path)
|
||||
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, mediaPath)
|
||||
if err == nil {
|
||||
exposureProgram64 := int64(*exposureProgram)
|
||||
newExif.ExposureProgram = &exposureProgram64
|
||||
@@ -147,7 +148,7 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
returnExif = &newExif
|
||||
log.Printf(
|
||||
"Incorrect GPS data in the %s Exif data: %f, %f, while expected values between '-90' and '90'. Ignoring GPS data.",
|
||||
media_path, long, lat)
|
||||
mediaPath, long, lat)
|
||||
return
|
||||
} else {
|
||||
newExif.GPSLatitude = &lat
|
||||
@@ -159,59 +160,59 @@ func (p internalExifParser) ParseExif(media_path string) (returnExif *models.Med
|
||||
return
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media_path string) (*string, error) {
|
||||
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, mediaPath string) (*string, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
||||
return nil, errors.Wrapf(err, couldNotReadXfromEXIFy, name, mediaPath)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.StringVal()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media_path)
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, mediaPath)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
log.Printf(warnEXIFtagXreturnedNully, name, mediaPath)
|
||||
return nil, ErrNullExifTag
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media_path string) (*big.Rat, error) {
|
||||
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, mediaPath string) (*big.Rat, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
||||
return nil, errors.Wrapf(err, couldNotReadXfromEXIFy, name, mediaPath)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.Rat(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media_path)
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, mediaPath)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
log.Printf(warnEXIFtagXreturnedNully, name, mediaPath)
|
||||
return nil, ErrNullExifTag
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media_path string) (*int, error) {
|
||||
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, mediaPath string) (*int, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
||||
return nil, errors.Wrapf(err, couldNotReadXfromEXIFy, name, mediaPath)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.Int(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media_path)
|
||||
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, mediaPath)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
log.Printf(warnEXIFtagXreturnedNully, name, mediaPath)
|
||||
return nil, ErrNullExifTag
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ func TestMain(m *testing.M) {
|
||||
func setPathWithCurrent(paths ...string) func() {
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
return func() {}
|
||||
return func() {
|
||||
// Return an empty function in case of error
|
||||
}
|
||||
}
|
||||
|
||||
base := filepath.Dir(file)
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/photoview/photoview/api/scanner/media_encoding/executable_worker"
|
||||
)
|
||||
|
||||
const testdataBinPath = "./testdata/bin"
|
||||
|
||||
func TestMagickCliNotExist(t *testing.T) {
|
||||
done := setPathWithCurrent()
|
||||
defer done()
|
||||
@@ -18,7 +20,7 @@ func TestMagickCliNotExist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMagickCliIgnore(t *testing.T) {
|
||||
donePath := setPathWithCurrent("./testdata/bin")
|
||||
donePath := setPathWithCurrent(testdataBinPath)
|
||||
defer donePath()
|
||||
|
||||
doneDisableRaw := setEnv("PHOTOVIEW_DISABLE_RAW_PROCESSING", "true")
|
||||
@@ -31,7 +33,7 @@ func TestMagickCliIgnore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMagickCliFail(t *testing.T) {
|
||||
donePath := setPathWithCurrent("./testdata/bin")
|
||||
donePath := setPathWithCurrent(testdataBinPath)
|
||||
defer donePath()
|
||||
|
||||
executable_worker.InitializeExecutableWorkers()
|
||||
@@ -53,7 +55,7 @@ func TestMagickCliFail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMagickCliSucceed(t *testing.T) {
|
||||
donePath := setPathWithCurrent("./testdata/bin")
|
||||
donePath := setPathWithCurrent(testdataBinPath)
|
||||
defer donePath()
|
||||
|
||||
executable_worker.InitializeExecutableWorkers()
|
||||
|
||||
@@ -52,9 +52,9 @@ func InitializePeriodicScanner(db *gorm.DB) error {
|
||||
}
|
||||
|
||||
func ChangePeriodicScanInterval(duration time.Duration) {
|
||||
var new_ticker *time.Ticker = nil
|
||||
var newTicker *time.Ticker = nil
|
||||
if duration > 0 {
|
||||
new_ticker = time.NewTicker(duration)
|
||||
newTicker = time.NewTicker(duration)
|
||||
log.Printf("Periodic scan interval changed: %s", duration.String())
|
||||
} else {
|
||||
log.Print("Periodic scan interval changed: disabled")
|
||||
@@ -68,7 +68,7 @@ func ChangePeriodicScanInterval(duration time.Duration) {
|
||||
mainPeriodicScanner.ticker.Stop()
|
||||
}
|
||||
|
||||
mainPeriodicScanner.ticker = new_ticker
|
||||
mainPeriodicScanner.ticker = newTicker
|
||||
mainPeriodicScanner.ticker_changed <- true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -118,7 +117,7 @@ func findMediaForAlbum(ctx scanner_task.TaskContext) ([]*models.Media, error) {
|
||||
|
||||
albumMedia := make([]*models.Media, 0)
|
||||
|
||||
dirContent, err := ioutil.ReadDir(ctx.GetAlbum().Path)
|
||||
dirContent, err := os.ReadDir(ctx.GetAlbum().Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +132,11 @@ func findMediaForAlbum(ctx scanner_task.TaskContext) ([]*models.Media, error) {
|
||||
}
|
||||
|
||||
if !item.IsDir() && !isDirSymlink && ctx.GetCache().IsPathMedia(mediaPath) {
|
||||
skip, err := scanner_tasks.Tasks.MediaFound(ctx, item, mediaPath)
|
||||
itemInfo, err := item.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
skip, err := scanner_tasks.Tasks.MediaFound(ctx, itemInfo, mediaPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testDataPath = "./test_data"
|
||||
|
||||
func TestNewRootPath(t *testing.T) {
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
@@ -21,7 +23,7 @@ func TestNewRootPath(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("Insert valid root album", func(t *testing.T) {
|
||||
album, err := scanner.NewRootAlbum(db, "./test_data", &user)
|
||||
album, err := scanner.NewRootAlbum(db, testDataPath, &user)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -33,7 +35,7 @@ func TestNewRootPath(t *testing.T) {
|
||||
|
||||
t.Run("Insert duplicate root album", func(t *testing.T) {
|
||||
|
||||
_, err := scanner.NewRootAlbum(db, "./test_data", &user)
|
||||
_, err := scanner.NewRootAlbum(db, testDataPath, &user)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "user already owns a path containing this path:")
|
||||
@@ -57,7 +59,7 @@ func TestNewRootPath(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
album, err := scanner.NewRootAlbum(db, "./test_data", &user2)
|
||||
album, err := scanner.NewRootAlbum(db, testDataPath, &user2)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -65,8 +67,8 @@ func TestNewRootPath(t *testing.T) {
|
||||
assert.NotNil(t, album)
|
||||
assert.Contains(t, album.Path, "/api/scanner/test_data")
|
||||
|
||||
owner_count := db.Model(&album).Association("Owners").Count()
|
||||
assert.EqualValues(t, 2, owner_count)
|
||||
ownerCount := db.Model(&album).Association("Owners").Count()
|
||||
assert.EqualValues(t, 2, ownerCount)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -27,25 +27,25 @@ func MakeAlbumCache() *AlbumScannerCache {
|
||||
}
|
||||
|
||||
// Insert single album directory in cache
|
||||
func (c *AlbumScannerCache) InsertAlbumPath(path string, contains_photo bool) {
|
||||
func (c *AlbumScannerCache) InsertAlbumPath(path string, containsPhoto bool) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.path_contains_photos[path] = contains_photo
|
||||
c.path_contains_photos[path] = containsPhoto
|
||||
}
|
||||
|
||||
// Insert album path and all parent directories up to the given root directory in cache
|
||||
func (c *AlbumScannerCache) InsertAlbumPaths(end_path string, root string, contains_photo bool) {
|
||||
curr_path := path.Clean(end_path)
|
||||
root_path := path.Clean(root)
|
||||
func (c *AlbumScannerCache) InsertAlbumPaths(endPath string, root string, containsPhoto bool) {
|
||||
currPath := path.Clean(endPath)
|
||||
rootPath := path.Clean(root)
|
||||
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
for curr_path != root_path || curr_path == "." {
|
||||
for currPath != rootPath || currPath == "." {
|
||||
|
||||
c.path_contains_photos[curr_path] = contains_photo
|
||||
c.path_contains_photos[currPath] = containsPhoto
|
||||
|
||||
curr_path = path.Dir(curr_path)
|
||||
currPath = path.Dir(currPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,11 +103,11 @@ func (c *AlbumScannerCache) GetAlbumIgnore(path string) *[]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AlbumScannerCache) InsertAlbumIgnore(path string, ignore_data []string) {
|
||||
func (c *AlbumScannerCache) InsertAlbumIgnore(path string, ignoreData []string) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.ignore_data[path] = ignore_data
|
||||
c.ignore_data[path] = ignoreData
|
||||
}
|
||||
|
||||
func (c *AlbumScannerCache) IsPathMedia(mediaPath string) bool {
|
||||
|
||||
@@ -71,17 +71,17 @@ func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *scanner_cache.
|
||||
// ProcessSingleMedia processes a single media, might be used to reprocess media with corrupted cache
|
||||
// Function waits for processing to finish before returning.
|
||||
func ProcessSingleMedia(db *gorm.DB, media *models.Media) error {
|
||||
album_cache := scanner_cache.MakeAlbumCache()
|
||||
albumCache := scanner_cache.MakeAlbumCache()
|
||||
|
||||
var album models.Album
|
||||
if err := db.Model(media).Association("Album").Find(&album); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
media_data := media_encoding.NewEncodeMediaData(media)
|
||||
mediaData := media_encoding.NewEncodeMediaData(media)
|
||||
|
||||
task_context := scanner_task.NewTaskContext(context.Background(), db, &album, album_cache)
|
||||
if err := scanMedia(task_context, media, &media_data, 0, 1); err != nil {
|
||||
taskContext := scanner_task.NewTaskContext(context.Background(), db, &album, albumCache)
|
||||
if err := scanMedia(taskContext, media, &mediaData, 0, 1); err != nil {
|
||||
return errors.Wrap(err, "single media scan")
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const globalScannerProgress = "global-scanner-progress"
|
||||
|
||||
// ScannerJob describes a job on the queue to be run by the scanner over a single album
|
||||
type ScannerJob struct {
|
||||
ctx scanner_task.TaskContext
|
||||
@@ -104,11 +106,11 @@ func (queue *ScannerQueue) startBackgroundWorker() {
|
||||
<-queue.idle_chan
|
||||
|
||||
queue.mutex.Lock()
|
||||
should_stop := queue.close_chan != nil && len(queue.in_progress) == 0 && len(queue.up_next) == 0
|
||||
shouldStop := queue.close_chan != nil && len(queue.in_progress) == 0 && len(queue.up_next) == 0
|
||||
queue.running = false
|
||||
queue.mutex.Unlock()
|
||||
|
||||
if should_stop {
|
||||
if shouldStop {
|
||||
*queue.close_chan <- true
|
||||
break
|
||||
}
|
||||
@@ -121,14 +123,14 @@ func (queue *ScannerQueue) startBackgroundWorker() {
|
||||
|
||||
func (queue *ScannerQueue) CloseBackgroundWorker() {
|
||||
queue.mutex.Lock()
|
||||
close_chan := make(chan bool)
|
||||
queue.close_chan = &close_chan
|
||||
closeChan := make(chan bool)
|
||||
queue.close_chan = &closeChan
|
||||
queue.mutex.Unlock()
|
||||
|
||||
queue.notify()
|
||||
|
||||
log.Println("Waiting for scanner background worker to finish all jobs...")
|
||||
<-close_chan
|
||||
<-closeChan
|
||||
}
|
||||
|
||||
func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
|
||||
@@ -162,14 +164,14 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
|
||||
}()
|
||||
}
|
||||
|
||||
in_progress_length := len(global_scanner_queue.in_progress)
|
||||
up_next_length := len(global_scanner_queue.up_next)
|
||||
inProgressLength := len(global_scanner_queue.in_progress)
|
||||
upNextLength := len(global_scanner_queue.up_next)
|
||||
|
||||
queue.mutex.Unlock()
|
||||
|
||||
if in_progress_length+up_next_length == 0 {
|
||||
if inProgressLength+upNextLength == 0 {
|
||||
notification.BroadcastNotification(&models.Notification{
|
||||
Key: "global-scanner-progress",
|
||||
Key: globalScannerProgress,
|
||||
Type: models.NotificationTypeMessage,
|
||||
Header: "Generating blurhashes",
|
||||
Content: "Generating blurhashes for newly scanned media",
|
||||
@@ -181,7 +183,7 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
|
||||
}
|
||||
|
||||
notification.BroadcastNotification(&models.Notification{
|
||||
Key: "global-scanner-progress",
|
||||
Key: globalScannerProgress,
|
||||
Type: models.NotificationTypeMessage,
|
||||
Header: "Scanner complete",
|
||||
Content: "All jobs have been scanned",
|
||||
@@ -190,10 +192,10 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
|
||||
} else {
|
||||
notifyThrottle.Trigger(func() {
|
||||
notification.BroadcastNotification(&models.Notification{
|
||||
Key: "global-scanner-progress",
|
||||
Key: globalScannerProgress,
|
||||
Type: models.NotificationTypeMessage,
|
||||
Header: "Scanning media",
|
||||
Content: fmt.Sprintf("%d jobs in progress\n%d jobs waiting", in_progress_length, up_next_length),
|
||||
Content: fmt.Sprintf("%d jobs in progress\n%d jobs waiting", inProgressLength, upNextLength),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -229,8 +231,8 @@ func AddAllToQueue() error {
|
||||
// AddUserToQueue finds all root albums owned by the given user and adds them to the scanner queue.
|
||||
// Function does not block.
|
||||
func AddUserToQueue(user *models.User) error {
|
||||
album_cache := scanner_cache.MakeAlbumCache()
|
||||
albums, album_errors := scanner.FindAlbumsForUser(global_scanner_queue.db, user, album_cache)
|
||||
albumCache := scanner_cache.MakeAlbumCache()
|
||||
albums, album_errors := scanner.FindAlbumsForUser(global_scanner_queue.db, user, albumCache)
|
||||
for _, err := range album_errors {
|
||||
return errors.Wrapf(err, "find albums for user (user_id: %d)", user.ID)
|
||||
}
|
||||
@@ -238,7 +240,7 @@ func AddUserToQueue(user *models.User) error {
|
||||
global_scanner_queue.mutex.Lock()
|
||||
for _, album := range albums {
|
||||
global_scanner_queue.addJob(&ScannerJob{
|
||||
ctx: scanner_task.NewTaskContext(context.Background(), global_scanner_queue.db, album, album_cache),
|
||||
ctx: scanner_task.NewTaskContext(context.Background(), global_scanner_queue.db, album, albumCache),
|
||||
})
|
||||
}
|
||||
global_scanner_queue.mutex.Unlock()
|
||||
|
||||
@@ -24,7 +24,7 @@ func makeScannerJob(albumID int) ScannerJob {
|
||||
return NewScannerJob(scanner_task.NewTaskContext(context.Background(), nil, makeAlbumWithID(albumID), scanner_cache.MakeAlbumCache()))
|
||||
}
|
||||
|
||||
func TestScannerQueue_AddJob(t *testing.T) {
|
||||
func TestScannerQueueAddJob(t *testing.T) {
|
||||
|
||||
scannerJobs := []ScannerJob{
|
||||
makeScannerJob(100),
|
||||
@@ -72,7 +72,7 @@ func TestScannerQueue_AddJob(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestScannerQueue_JobOnQueue(t *testing.T) {
|
||||
func TestScannerQueueJobOnQueue(t *testing.T) {
|
||||
|
||||
scannerJobs := []ScannerJob{
|
||||
makeScannerJob(100),
|
||||
|
||||
@@ -30,23 +30,23 @@ func TestCleanupMedia(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
test_dir := t.TempDir()
|
||||
assert.NoError(t, copy.Copy("../../test_data", test_dir))
|
||||
testDir := t.TempDir()
|
||||
assert.NoError(t, copy.Copy("../../test_data", testDir))
|
||||
|
||||
countAllMedia := func() int {
|
||||
var all_media []*models.Media
|
||||
if !assert.NoError(t, db.Find(&all_media).Error) {
|
||||
var allMedia []*models.Media
|
||||
if !assert.NoError(t, db.Find(&allMedia).Error) {
|
||||
return -1
|
||||
}
|
||||
return len(all_media)
|
||||
return len(allMedia)
|
||||
}
|
||||
|
||||
countAllMediaURLs := func() int {
|
||||
var all_media_urls []*models.MediaURL
|
||||
if !assert.NoError(t, db.Find(&all_media_urls).Error) {
|
||||
var allMediaURLs []*models.MediaURL
|
||||
if !assert.NoError(t, db.Find(&allMediaURLs).Error) {
|
||||
return -1
|
||||
}
|
||||
return len(all_media_urls)
|
||||
return len(allMediaURLs)
|
||||
}
|
||||
|
||||
pass := "1234"
|
||||
@@ -60,20 +60,20 @@ func TestCleanupMedia(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
root_album := models.Album{
|
||||
rootAlbum := models.Album{
|
||||
Title: "root album",
|
||||
Path: test_dir,
|
||||
Path: testDir,
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&root_album).Error) {
|
||||
if !assert.NoError(t, db.Save(&rootAlbum).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Model(user1).Association("Albums").Append(&root_album)
|
||||
err = db.Model(user1).Association("Albums").Append(&rootAlbum)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
err = db.Model(user2).Association("Albums").Append(&root_album)
|
||||
err = db.Model(user2).Association("Albums").Append(&rootAlbum)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -84,25 +84,25 @@ func TestCleanupMedia(t *testing.T) {
|
||||
assert.Equal(t, 18, countAllMediaURLs())
|
||||
|
||||
// move faces directory
|
||||
assert.NoError(t, os.Rename(path.Join(test_dir, "faces"), path.Join(test_dir, "faces_moved")))
|
||||
assert.NoError(t, os.Rename(path.Join(testDir, "faces"), path.Join(testDir, "faces_moved")))
|
||||
test_utils.RunScannerAll(t, db)
|
||||
assert.Equal(t, 9, countAllMedia())
|
||||
assert.Equal(t, 18, countAllMediaURLs())
|
||||
|
||||
// remove faces_moved directory
|
||||
assert.NoError(t, os.RemoveAll(path.Join(test_dir, "faces_moved")))
|
||||
assert.NoError(t, os.RemoveAll(path.Join(testDir, "faces_moved")))
|
||||
test_utils.RunScannerAll(t, db)
|
||||
assert.Equal(t, 3, countAllMedia())
|
||||
assert.Equal(t, 6, countAllMediaURLs())
|
||||
})
|
||||
|
||||
t.Run("Modify images", func(t *testing.T) {
|
||||
assert.NoError(t, os.Rename(path.Join(test_dir, "buttercup_close_summer_yellow.jpg"), path.Join(test_dir, "yellow-flower.jpg")))
|
||||
assert.NoError(t, os.Rename(path.Join(testDir, "buttercup_close_summer_yellow.jpg"), path.Join(testDir, "yellow-flower.jpg")))
|
||||
test_utils.RunScannerAll(t, db)
|
||||
assert.Equal(t, 3, countAllMedia())
|
||||
assert.Equal(t, 6, countAllMediaURLs())
|
||||
|
||||
assert.NoError(t, os.Remove(path.Join(test_dir, "lilac_lilac_bush_lilac.jpg")))
|
||||
assert.NoError(t, os.Remove(path.Join(testDir, "lilac_lilac_bush_lilac.jpg")))
|
||||
test_utils.RunScannerAll(t, db)
|
||||
assert.Equal(t, 2, countAllMedia())
|
||||
assert.Equal(t, 4, countAllMediaURLs())
|
||||
|
||||
@@ -12,8 +12,8 @@ type MediaCleanupTask struct {
|
||||
|
||||
func (t MediaCleanupTask) AfterScanAlbum(ctx scanner_task.TaskContext, changedMedia []*models.Media, albumMedia []*models.Media) error {
|
||||
|
||||
cleanup_errors := CleanupMedia(ctx.GetDB(), ctx.GetAlbum().ID, albumMedia)
|
||||
for _, err := range cleanup_errors {
|
||||
cleanupErrors := CleanupMedia(ctx.GetDB(), ctx.GetAlbum().ID, albumMedia)
|
||||
for _, err := range cleanupErrors {
|
||||
scanner_utils.ScannerError("delete old media: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ func (t ProcessPhotoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||
return []*models.MediaURL{}, errors.Wrap(err, "error processing photo highres")
|
||||
}
|
||||
|
||||
var photoDimensions *media_utils.PhotoDimensions
|
||||
var baseImagePath string = photo.Path
|
||||
|
||||
// Generate high res jpeg
|
||||
@@ -92,16 +91,15 @@ func (t ProcessPhotoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||
}
|
||||
}
|
||||
|
||||
var photoDimensions *media_utils.PhotoDimensions
|
||||
// Save original photo to database
|
||||
if origURL == nil {
|
||||
|
||||
// Make sure photo dimensions is set
|
||||
if photoDimensions == nil {
|
||||
photoDimensions, err = media_utils.GetPhotoDimensions(baseImagePath)
|
||||
if err != nil {
|
||||
return []*models.MediaURL{}, err
|
||||
}
|
||||
}
|
||||
|
||||
original, err := saveOriginalPhotoToDB(ctx.GetDB(), photo, mediaData, photoDimensions)
|
||||
if err != nil {
|
||||
|
||||
@@ -87,12 +87,12 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||
}
|
||||
|
||||
if videoWebURL == nil && !videoType.IsWebCompatible() {
|
||||
web_video_name := fmt.Sprintf("web_video_%s_%s", path.Base(video.Path), utils.GenerateToken())
|
||||
web_video_name = strings.ReplaceAll(web_video_name, ".", "_")
|
||||
web_video_name = strings.ReplaceAll(web_video_name, " ", "_")
|
||||
web_video_name = web_video_name + ".mp4"
|
||||
webVideoName := fmt.Sprintf("web_video_%s_%s", path.Base(video.Path), utils.GenerateToken())
|
||||
webVideoName = strings.ReplaceAll(webVideoName, ".", "_")
|
||||
webVideoName = strings.ReplaceAll(webVideoName, " ", "_")
|
||||
webVideoName = webVideoName + ".mp4"
|
||||
|
||||
webVideoPath := path.Join(mediaCachePath, web_video_name)
|
||||
webVideoPath := path.Join(mediaCachePath, webVideoName)
|
||||
|
||||
err = executable_worker.FfmpegCli.EncodeMp4(video.Path, webVideoPath)
|
||||
if err != nil {
|
||||
@@ -111,7 +111,7 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||
|
||||
mediaURL := models.MediaURL{
|
||||
MediaID: video.ID,
|
||||
MediaName: web_video_name,
|
||||
MediaName: webVideoName,
|
||||
Width: webMetadata.Width,
|
||||
Height: webMetadata.Height,
|
||||
Purpose: models.VideoWeb,
|
||||
@@ -132,12 +132,12 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||
}
|
||||
|
||||
if videoThumbnailURL == nil {
|
||||
video_thumb_name := fmt.Sprintf("video_thumb_%s_%s", path.Base(video.Path), utils.GenerateToken())
|
||||
video_thumb_name = strings.ReplaceAll(video_thumb_name, ".", "_")
|
||||
video_thumb_name = strings.ReplaceAll(video_thumb_name, " ", "_")
|
||||
video_thumb_name = video_thumb_name + ".jpg"
|
||||
videoThumbName := fmt.Sprintf("video_thumb_%s_%s", path.Base(video.Path), utils.GenerateToken())
|
||||
videoThumbName = strings.ReplaceAll(videoThumbName, ".", "_")
|
||||
videoThumbName = strings.ReplaceAll(videoThumbName, " ", "_")
|
||||
videoThumbName = videoThumbName + ".jpg"
|
||||
|
||||
thumbImagePath := path.Join(mediaCachePath, video_thumb_name)
|
||||
thumbImagePath := path.Join(mediaCachePath, videoThumbName)
|
||||
|
||||
err = executable_worker.FfmpegCli.EncodeVideoThumbnail(video.Path, thumbImagePath, probeData)
|
||||
if err != nil {
|
||||
@@ -156,7 +156,7 @@ func (t ProcessVideoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||
|
||||
thumbMediaURL := models.MediaURL{
|
||||
MediaID: video.ID,
|
||||
MediaName: video_thumb_name,
|
||||
MediaName: videoThumbName,
|
||||
Width: thumbDimensions.Width,
|
||||
Height: thumbDimensions.Height,
|
||||
Purpose: models.VideoThumbnail,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *media_encoding.EncodeMediaData, highres_name string, imagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) {
|
||||
func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *media_encoding.EncodeMediaData, highResName string, imagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) {
|
||||
|
||||
err := imageData.EncodeHighRes(imagePath)
|
||||
if err != nil {
|
||||
@@ -32,7 +32,7 @@ func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *media_
|
||||
|
||||
mediaURL = &models.MediaURL{
|
||||
MediaID: media.ID,
|
||||
MediaName: highres_name,
|
||||
MediaName: highResName,
|
||||
Width: photoDimensions.Width,
|
||||
Height: photoDimensions.Height,
|
||||
Purpose: models.PhotoHighRes,
|
||||
@@ -41,7 +41,7 @@ func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *media_
|
||||
}
|
||||
|
||||
if err := tx.Create(&mediaURL).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "could not insert highres media url (%d, %s)", media.ID, highres_name)
|
||||
return nil, errors.Wrapf(err, "could not insert highres media url (%d, %s)", media.ID, highResName)
|
||||
}
|
||||
} else {
|
||||
mediaURL.Width = photoDimensions.Width
|
||||
@@ -49,15 +49,15 @@ func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *media_
|
||||
mediaURL.FileSize = fileStats.Size()
|
||||
|
||||
if err := tx.Save(&mediaURL).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "could not update media url after side car changes (%d, %s)", media.ID, highres_name)
|
||||
return nil, errors.Wrapf(err, "could not update media url after side car changes (%d, %s)", media.ID, highResName)
|
||||
}
|
||||
}
|
||||
|
||||
return mediaURL, nil
|
||||
}
|
||||
|
||||
func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnail_name string, photoCachePath string, baseImagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) {
|
||||
thumbOutputPath := path.Join(photoCachePath, thumbnail_name)
|
||||
func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnailName string, photoCachePath string, baseImagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) {
|
||||
thumbOutputPath := path.Join(photoCachePath, thumbnailName)
|
||||
|
||||
thumbSize, err := media_encoding.EncodeThumbnail(tx, baseImagePath, thumbOutputPath)
|
||||
if err != nil {
|
||||
@@ -73,7 +73,7 @@ func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnail_name
|
||||
|
||||
mediaURL = &models.MediaURL{
|
||||
MediaID: media.ID,
|
||||
MediaName: thumbnail_name,
|
||||
MediaName: thumbnailName,
|
||||
Width: thumbSize.Width,
|
||||
Height: thumbSize.Height,
|
||||
Purpose: models.PhotoThumbnail,
|
||||
@@ -82,7 +82,7 @@ func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnail_name
|
||||
}
|
||||
|
||||
if err := tx.Create(&mediaURL).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "could not insert thumbnail media url (%d, %s)", media.ID, thumbnail_name)
|
||||
return nil, errors.Wrapf(err, "could not insert thumbnail media url (%d, %s)", media.ID, thumbnailName)
|
||||
}
|
||||
} else {
|
||||
mediaURL.Width = thumbSize.Width
|
||||
@@ -90,7 +90,7 @@ func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnail_name
|
||||
mediaURL.FileSize = fileStats.Size()
|
||||
|
||||
if err := tx.Save(&mediaURL).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "could not update media url after side car changes (%d, %s)", media.ID, thumbnail_name)
|
||||
return nil, errors.Wrapf(err, "could not update media url after side car changes (%d, %s)", media.ID, thumbnailName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,16 +25,16 @@ func TestFullScan(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
root_album := models.Album{
|
||||
rootAlbum := models.Album{
|
||||
Title: "root album",
|
||||
Path: "./test_data",
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&root_album).Error) {
|
||||
if !assert.NoError(t, db.Save(&rootAlbum).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Model(user).Association("Albums").Append(&root_album)
|
||||
err = db.Model(user).Association("Albums").Append(&rootAlbum)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -45,37 +45,37 @@ func TestFullScan(t *testing.T) {
|
||||
|
||||
test_utils.RunScannerOnUser(t, db, user)
|
||||
|
||||
var all_media []*models.Media
|
||||
if !assert.NoError(t, db.Find(&all_media).Error) {
|
||||
var allMedia []*models.Media
|
||||
if !assert.NoError(t, db.Find(&allMedia).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 9, len(all_media))
|
||||
assert.Equal(t, 9, len(allMedia))
|
||||
|
||||
var all_media_url []*models.MediaURL
|
||||
if !assert.NoError(t, db.Find(&all_media_url).Error) {
|
||||
var allMediaURL []*models.MediaURL
|
||||
if !assert.NoError(t, db.Find(&allMediaURL).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 18, len(all_media_url))
|
||||
assert.Equal(t, 18, len(allMediaURL))
|
||||
|
||||
// Verify that faces was recognized
|
||||
assert.Eventually(t, func() bool {
|
||||
var all_face_groups []*models.FaceGroup
|
||||
if !assert.NoError(t, db.Find(&all_face_groups).Error) {
|
||||
var allFaceGroups []*models.FaceGroup
|
||||
if !assert.NoError(t, db.Find(&allFaceGroups).Error) {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(all_face_groups) == 3
|
||||
return len(allFaceGroups) == 3
|
||||
}, time.Second*5, time.Millisecond*500)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
var all_image_faces []*models.ImageFace
|
||||
if !assert.NoError(t, db.Find(&all_image_faces).Error) {
|
||||
var allImageFaces []*models.ImageFace
|
||||
if !assert.NoError(t, db.Find(&allImageFaces).Error) {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(all_image_faces) == 6
|
||||
return len(allImageFaces) == 6
|
||||
}, time.Second*5, time.Millisecond*500)
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package scanner
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -43,7 +42,7 @@ func getPhotoviewIgnore(ignorePath string) ([]string, error) {
|
||||
return photoviewIgnore, scanner.Err()
|
||||
}
|
||||
|
||||
func FindAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cache.AlbumScannerCache) ([]*models.Album, []error) {
|
||||
func FindAlbumsForUser(db *gorm.DB, user *models.User, albumCache *scanner_cache.AlbumScannerCache) ([]*models.Album, []error) {
|
||||
|
||||
if err := user.FillAlbums(db); err != nil {
|
||||
return nil, []error{err}
|
||||
@@ -101,7 +100,7 @@ func FindAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach
|
||||
albumIgnore := albumInfo.ignore
|
||||
|
||||
// Read path
|
||||
dirContent, err := ioutil.ReadDir(albumPath)
|
||||
dirContent, err := os.ReadDir(albumPath)
|
||||
if err != nil {
|
||||
scanErrors = append(scanErrors, errors.Wrapf(err, "read directory (%s)", albumPath))
|
||||
continue
|
||||
@@ -156,7 +155,7 @@ func FindAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach
|
||||
}
|
||||
|
||||
// Store album ignore
|
||||
album_cache.InsertAlbumIgnore(albumPath, albumIgnore)
|
||||
albumCache.InsertAlbumIgnore(albumPath, albumIgnore)
|
||||
|
||||
if err := tx.Create(&album).Error; err != nil {
|
||||
return errors.Wrap(err, "insert album into database")
|
||||
@@ -182,7 +181,7 @@ func FindAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach
|
||||
}
|
||||
|
||||
// Update album ignore
|
||||
album_cache.InsertAlbumIgnore(albumPath, albumIgnore)
|
||||
albumCache.InsertAlbumIgnore(albumPath, albumIgnore)
|
||||
}
|
||||
|
||||
userAlbums = append(userAlbums, album)
|
||||
@@ -210,7 +209,7 @@ func FindAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach
|
||||
continue
|
||||
}
|
||||
|
||||
if (item.IsDir() || isDirSymlink) && directoryContainsPhotos(subalbumPath, album_cache, albumIgnore) {
|
||||
if (item.IsDir() || isDirSymlink) && directoryContainsPhotos(subalbumPath, albumCache, albumIgnore) {
|
||||
scanQueue.PushBack(scanInfo{
|
||||
path: subalbumPath,
|
||||
parent: album,
|
||||
@@ -228,21 +227,21 @@ func FindAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach
|
||||
|
||||
func directoryContainsPhotos(rootPath string, cache *scanner_cache.AlbumScannerCache, albumIgnore []string) bool {
|
||||
|
||||
if contains_image := cache.AlbumContainsPhotos(rootPath); contains_image != nil {
|
||||
return *contains_image
|
||||
if containsImage := cache.AlbumContainsPhotos(rootPath); containsImage != nil {
|
||||
return *containsImage
|
||||
}
|
||||
|
||||
scanQueue := list.New()
|
||||
scanQueue.PushBack(rootPath)
|
||||
|
||||
scanned_directories := make([]string, 0)
|
||||
scannedDirectories := make([]string, 0)
|
||||
|
||||
for scanQueue.Front() != nil {
|
||||
|
||||
dirPath := scanQueue.Front().Value.(string)
|
||||
scanQueue.Remove(scanQueue.Front())
|
||||
|
||||
scanned_directories = append(scanned_directories, dirPath)
|
||||
scannedDirectories = append(scannedDirectories, dirPath)
|
||||
|
||||
// Update ignore dir list
|
||||
photoviewIgnore, err := getPhotoviewIgnore(dirPath)
|
||||
@@ -253,7 +252,7 @@ func directoryContainsPhotos(rootPath string, cache *scanner_cache.AlbumScannerC
|
||||
}
|
||||
ignoreEntries := ignore.CompileIgnoreLines(albumIgnore...)
|
||||
|
||||
dirContent, err := ioutil.ReadDir(dirPath)
|
||||
dirContent, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
scanner_utils.ScannerError("Could not read directory (%s): %s\n", dirPath, err.Error())
|
||||
return false
|
||||
@@ -285,7 +284,7 @@ func directoryContainsPhotos(rootPath string, cache *scanner_cache.AlbumScannerC
|
||||
|
||||
}
|
||||
|
||||
for _, scanned_path := range scanned_directories {
|
||||
for _, scanned_path := range scannedDirectories {
|
||||
log.Printf("Insert Album %s, contains photo is false", scanned_path)
|
||||
cache.InsertAlbumPath(scanned_path, false)
|
||||
}
|
||||
|
||||
@@ -37,16 +37,16 @@ func CachePathForMedia(albumID int, mediaID int) (string, error) {
|
||||
return photoCachePath, nil
|
||||
}
|
||||
|
||||
var test_cache_path string = ""
|
||||
var testCachePath string = ""
|
||||
|
||||
func ConfigureTestCache(tmp_dir string) {
|
||||
test_cache_path = tmp_dir
|
||||
func ConfigureTestCache(tmpDir string) {
|
||||
testCachePath = tmpDir
|
||||
}
|
||||
|
||||
// MediaCachePath returns the path for where the media cache is located on the file system
|
||||
func MediaCachePath() string {
|
||||
if test_cache_path != "" {
|
||||
return test_cache_path
|
||||
if testCachePath != "" {
|
||||
return testCachePath
|
||||
}
|
||||
|
||||
photoCache := EnvMediaCachePath.GetValue()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
@@ -18,7 +17,7 @@ func TestIsDirSymlink(t *testing.T) {
|
||||
test_utils.FilesystemTest(t)
|
||||
|
||||
// Prepare a temporary directory for testing purposes
|
||||
dir, err := ioutil.TempDir("", "testing")
|
||||
dir, err := os.MkdirTemp("", "testing")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory for testing")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user