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}