lens @ 72ec551e6cb422531e543e3fb431324aed5ac025

  1package sql
  2
  3import (
  4	"context"
  5	"time"
  6
  7	"gorm.io/gorm"
  8
  9	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 10	"git.sr.ht/~gabrielgio/img/pkg/list"
 11)
 12
 13type (
 14	Media struct {
 15		gorm.Model
 16		Name     string `gorm:"not null"`
 17		Path     string `gorm:"not null;unique"`
 18		PathHash string `gorm:"not null;unique"`
 19		MIMEType string `gorm:"not null"`
 20	}
 21
 22	MediaEXIF struct {
 23		gorm.Model
 24		Width           *float64
 25		Height          *float64
 26		MediaID         uint
 27		Media           Media
 28		Description     *string
 29		Camera          *string
 30		Maker           *string
 31		Lens            *string
 32		DateShot        *time.Time
 33		Exposure        *float64
 34		Aperture        *float64
 35		Iso             *int64
 36		FocalLength     *float64
 37		Flash           *int64
 38		Orientation     *int64
 39		ExposureProgram *int64
 40		GPSLatitude     *float64
 41		GPSLongitude    *float64
 42	}
 43
 44	MediaThumbnail struct {
 45		gorm.Model
 46		Path    string
 47		MediaID uint
 48		Media   Media
 49	}
 50
 51	MediaAlbum struct {
 52		gorm.Model
 53		ParentID *uint
 54		Parent   *MediaAlbum
 55		Name     string
 56		Path     string
 57	}
 58
 59	MediaAlbumFile struct {
 60		gorm.Model
 61		MediaID uint
 62		Media   Media
 63		AlbumID uint
 64		Album   MediaAlbum
 65	}
 66
 67	MediaRepository struct {
 68		db *gorm.DB
 69	}
 70)
 71
 72var _ repository.MediaRepository = &MediaRepository{}
 73
 74func (m *Media) ToModel() *repository.Media {
 75	return &repository.Media{
 76		ID:       m.ID,
 77		Path:     m.Path,
 78		PathHash: m.PathHash,
 79		Name:     m.Name,
 80		MIMEType: m.MIMEType,
 81	}
 82}
 83
 84func (m *MediaEXIF) ToModel() *repository.MediaEXIF {
 85	return &repository.MediaEXIF{
 86		Height:          m.Height,
 87		Width:           m.Width,
 88		Description:     m.Description,
 89		Camera:          m.Camera,
 90		Maker:           m.Maker,
 91		Lens:            m.Lens,
 92		DateShot:        m.DateShot,
 93		Exposure:        m.Exposure,
 94		Aperture:        m.Aperture,
 95		Iso:             m.Iso,
 96		FocalLength:     m.FocalLength,
 97		Flash:           m.Flash,
 98		Orientation:     m.Orientation,
 99		ExposureProgram: m.ExposureProgram,
100		GPSLatitude:     m.GPSLatitude,
101		GPSLongitude:    m.GPSLongitude,
102	}
103}
104
105func (a *MediaAlbum) ToModel() *repository.Album {
106	return &repository.Album{
107		ID: a.ID,
108	}
109}
110
111func (m *MediaThumbnail) ToModel() *repository.MediaThumbnail {
112	return &repository.MediaThumbnail{
113		Path: m.Path,
114	}
115}
116
117func NewMediaRepository(db *gorm.DB) *MediaRepository {
118	return &MediaRepository{
119		db: db,
120	}
121}
122
123func (self *MediaRepository) Create(ctx context.Context, createMedia *repository.CreateMedia) error {
124	media := &Media{
125		Name:     createMedia.Name,
126		Path:     createMedia.Path,
127		PathHash: createMedia.PathHash,
128		MIMEType: createMedia.MIMEType,
129	}
130
131	result := self.db.
132		WithContext(ctx).
133		Create(media)
134	if result.Error != nil {
135		return result.Error
136	}
137
138	return nil
139}
140
141func (self *MediaRepository) Exists(ctx context.Context, path string) (bool, error) {
142	var exists bool
143	result := self.db.
144		WithContext(ctx).
145		Model(&Media{}).
146		Select("count(id) > 0").
147		Where("path_hash = ?", path).
148		Find(&exists)
149
150	if result.Error != nil {
151		return false, result.Error
152	}
153
154	return exists, nil
155}
156
157func (self *MediaRepository) List(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
158	medias := make([]*Media, 0)
159	result := self.db.
160		WithContext(ctx).
161		Model(&Media{}).
162		Offset(pagination.Page * pagination.Size).
163		Limit(pagination.Size).
164		Where("path like '" + pagination.Path + "%'").
165		Order("created_at DESC").
166		Find(&medias)
167
168	if result.Error != nil {
169		return nil, result.Error
170	}
171
172	m := list.Map(medias, func(s *Media) *repository.Media {
173		return s.ToModel()
174	})
175
176	return m, nil
177}
178
179func (self *MediaRepository) Get(ctx context.Context, pathHash string) (*repository.Media, error) {
180	m := &Media{}
181	result := self.db.
182		WithContext(ctx).
183		Model(&Media{}).
184		Where("path_hash = ?", pathHash).
185		Limit(1).
186		Take(m)
187
188	if result.Error != nil {
189		return nil, result.Error
190	}
191
192	return m.ToModel(), nil
193}
194
195func (self *MediaRepository) GetPath(ctx context.Context, pathHash string) (string, error) {
196	var path string
197	result := self.db.
198		WithContext(ctx).
199		Model(&Media{}).
200		Select("path").
201		Where("path_hash = ?", pathHash).
202		Limit(1).
203		Find(&path)
204
205	if result.Error != nil {
206		return "", result.Error
207	}
208
209	return path, nil
210}
211
212func (self *MediaRepository) GetThumbnailPath(ctx context.Context, pathHash string) (string, error) {
213	var path string
214	result := self.db.
215		WithContext(ctx).
216		Model(&Media{}).
217		Select("media_thumbnails.path").
218		Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
219		Where("media.path_hash = ?", pathHash).
220		Limit(1).
221		Find(&path)
222
223	if result.Error != nil {
224		return "", result.Error
225	}
226
227	return path, nil
228}
229
230func (m *MediaRepository) GetEXIF(ctx context.Context, mediaID uint) (*repository.MediaEXIF, error) {
231	exif := &MediaEXIF{}
232	result := m.db.
233		WithContext(ctx).
234		Model(&Media{}).
235		Where("media_id = ?", mediaID).
236		Limit(1).
237		Take(m)
238
239	if result.Error != nil {
240		return nil, result.Error
241	}
242
243	return exif.ToModel(), nil
244}
245
246func (s *MediaRepository) CreateEXIF(ctx context.Context, id uint, info *repository.MediaEXIF) error {
247	media := &MediaEXIF{
248		MediaID:         id,
249		Width:           info.Width,
250		Height:          info.Height,
251		Description:     info.Description,
252		Camera:          info.Camera,
253		Maker:           info.Maker,
254		Lens:            info.Lens,
255		DateShot:        info.DateShot,
256		Exposure:        info.Exposure,
257		Aperture:        info.Aperture,
258		Iso:             info.Iso,
259		FocalLength:     info.FocalLength,
260		Flash:           info.Flash,
261		Orientation:     info.Orientation,
262		ExposureProgram: info.ExposureProgram,
263		GPSLatitude:     info.GPSLatitude,
264		GPSLongitude:    info.GPSLongitude,
265	}
266
267	result := s.db.
268		WithContext(ctx).
269		Create(media)
270	if result.Error != nil {
271		return result.Error
272	}
273
274	return nil
275}
276
277func (r *MediaRepository) ListEmptyEXIF(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
278	medias := make([]*Media, 0)
279	result := r.db.
280		WithContext(ctx).
281		Model(&Media{}).
282		Joins("left join media_exifs on media.id = media_exifs.media_id").
283		Where("media_exifs.media_id IS NULL AND media.path like '" + pagination.Path + "%'").
284		Offset(pagination.Page * pagination.Size).
285		Limit(pagination.Size).
286		Order("media.created_at DESC").
287		Find(&medias)
288
289	if result.Error != nil {
290		return nil, result.Error
291	}
292
293	m := list.Map(medias, func(s *Media) *repository.Media {
294		return s.ToModel()
295	})
296
297	return m, nil
298}
299
300func (r *MediaRepository) ListEmptyThumbnail(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
301	medias := make([]*Media, 0)
302	result := r.db.
303		WithContext(ctx).
304		Model(&Media{}).
305		Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
306		Where("media_thumbnails.media_id IS NULL AND media.path like '" + pagination.Path + "%'").
307		Offset(pagination.Page * pagination.Size).
308		Limit(pagination.Size).
309		Order("media.created_at DESC").
310		Find(&medias)
311
312	if result.Error != nil {
313		return nil, result.Error
314	}
315
316	m := list.Map(medias, func(s *Media) *repository.Media {
317		return s.ToModel()
318	})
319
320	return m, nil
321}
322
323func (m *MediaRepository) GetThumbnail(ctx context.Context, mediaID uint) (*repository.MediaThumbnail, error) {
324	thumbnail := &MediaThumbnail{}
325	result := m.db.
326		WithContext(ctx).
327		Model(&Media{}).
328		Where("media_id = ?", mediaID).
329		Limit(1).
330		Take(m)
331
332	if result.Error != nil {
333		return nil, result.Error
334	}
335
336	return thumbnail.ToModel(), nil
337}
338
339func (m *MediaRepository) CreateThumbnail(ctx context.Context, mediaID uint, thumbnail *repository.MediaThumbnail) error {
340	media := &MediaThumbnail{
341		MediaID: mediaID,
342		Path:    thumbnail.Path,
343	}
344
345	result := m.db.
346		WithContext(ctx).
347		Create(media)
348	if result.Error != nil {
349		return result.Error
350	}
351
352	return nil
353}
354
355func (r *MediaRepository) ListEmptyAlbums(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
356	medias := make([]*Media, 0)
357	result := r.db.
358		WithContext(ctx).
359		Model(&Media{}).
360		Joins("left join media_album_files on media.id = media_album_files.media_id").
361		Where("media_album_files.media_id IS NULL").
362		Offset(pagination.Page * pagination.Size).
363		Limit(pagination.Size).
364		Order("media.created_at DESC").
365		Find(&medias)
366
367	if result.Error != nil {
368		return nil, result.Error
369	}
370
371	m := list.Map(medias, func(s *Media) *repository.Media {
372		return s.ToModel()
373	})
374
375	return m, nil
376}
377
378func (m *MediaRepository) ExistsAlbumByAbsolutePath(ctx context.Context, path string) (bool, error) {
379	var exists bool
380	result := m.db.
381		WithContext(ctx).
382		Model(&MediaAlbum{}).
383		Select("count(id) > 0").
384		Where("path = ?", path).
385		Find(&exists)
386
387	if result.Error != nil {
388		return false, result.Error
389	}
390
391	return exists, nil
392}
393
394func (r *MediaRepository) GetAlbumByAbsolutePath(ctx context.Context, path string) (*repository.Album, error) {
395	m := &MediaAlbum{}
396	result := r.db.
397		WithContext(ctx).
398		Model(&MediaAlbum{}).
399		Where("path = ?", path).
400		Limit(1).
401		Take(m)
402
403	if result.Error != nil {
404		return nil, result.Error
405	}
406
407	return m.ToModel(), nil
408}
409
410func (m *MediaRepository) CreateAlbum(ctx context.Context, createAlbum *repository.CreateAlbum) (*repository.Album, error) {
411	album := &MediaAlbum{
412		ParentID: createAlbum.ParentID,
413		Name:     createAlbum.Name,
414		Path:     createAlbum.Path,
415	}
416
417	result := m.db.
418		WithContext(ctx).
419		Create(album)
420	if result.Error != nil {
421		return nil, result.Error
422	}
423
424	return album.ToModel(), nil
425}
426
427func (m *MediaRepository) CreateAlbumFile(ctx context.Context, createAlbumFile *repository.CreateAlbumFile) error {
428	albumFile := &MediaAlbumFile{
429		MediaID: createAlbumFile.MediaID,
430		AlbumID: createAlbumFile.AlbumID,
431	}
432
433	result := m.db.
434		WithContext(ctx).
435		Create(albumFile)
436	if result.Error != nil {
437		return result.Error
438	}
439
440	return nil
441}