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 MediaRepository struct {
52 db *gorm.DB
53 }
54)
55
56var _ repository.MediaRepository = &MediaRepository{}
57
58func (self *Media) ToModel() *repository.Media {
59 return &repository.Media{
60 ID: self.ID,
61 Path: self.Path,
62 PathHash: self.PathHash,
63 Name: self.Name,
64 MIMEType: self.MIMEType,
65 }
66}
67
68func (m *MediaEXIF) ToModel() *repository.MediaEXIF {
69 return &repository.MediaEXIF{
70 Height: m.Height,
71 Width: m.Width,
72 Description: m.Description,
73 Camera: m.Camera,
74 Maker: m.Maker,
75 Lens: m.Lens,
76 DateShot: m.DateShot,
77 Exposure: m.Exposure,
78 Aperture: m.Aperture,
79 Iso: m.Iso,
80 FocalLength: m.FocalLength,
81 Flash: m.Flash,
82 Orientation: m.Orientation,
83 ExposureProgram: m.ExposureProgram,
84 GPSLatitude: m.GPSLatitude,
85 GPSLongitude: m.GPSLongitude,
86 }
87}
88
89func (m *MediaThumbnail) ToModel() *repository.MediaThumbnail {
90 return &repository.MediaThumbnail{
91 Path: m.Path,
92 }
93}
94
95func NewMediaRepository(db *gorm.DB) *MediaRepository {
96 return &MediaRepository{
97 db: db,
98 }
99}
100
101func (self *MediaRepository) Create(ctx context.Context, createMedia *repository.CreateMedia) error {
102 media := &Media{
103 Name: createMedia.Name,
104 Path: createMedia.Path,
105 PathHash: createMedia.PathHash,
106 MIMEType: createMedia.MIMEType,
107 }
108
109 result := self.db.
110 WithContext(ctx).
111 Create(media)
112 if result.Error != nil {
113 return result.Error
114 }
115
116 return nil
117}
118
119func (self *MediaRepository) Exists(ctx context.Context, path string) (bool, error) {
120 var exists bool
121 result := self.db.
122 WithContext(ctx).
123 Model(&Media{}).
124 Select("count(id) > 0").
125 Where("path_hash = ?", path).
126 Find(&exists)
127
128 if result.Error != nil {
129 return false, result.Error
130 }
131
132 return exists, nil
133}
134
135func (self *MediaRepository) List(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
136 medias := make([]*Media, 0)
137 result := self.db.
138 WithContext(ctx).
139 Model(&Media{}).
140 Offset(pagination.Page * pagination.Size).
141 Limit(pagination.Size).
142 Where("path like '" + pagination.Path + "%'").
143 Order("created_at DESC").
144 Find(&medias)
145
146 if result.Error != nil {
147 return nil, result.Error
148 }
149
150 m := list.Map(medias, func(s *Media) *repository.Media {
151 return s.ToModel()
152 })
153
154 return m, nil
155}
156
157func (self *MediaRepository) Get(ctx context.Context, pathHash string) (*repository.Media, error) {
158 m := &Media{}
159 result := self.db.
160 WithContext(ctx).
161 Model(&Media{}).
162 Where("path_hash = ?", pathHash).
163 Limit(1).
164 Take(m)
165
166 if result.Error != nil {
167 return nil, result.Error
168 }
169
170 return m.ToModel(), nil
171}
172
173func (self *MediaRepository) GetPath(ctx context.Context, pathHash string) (string, error) {
174 var path string
175 result := self.db.
176 WithContext(ctx).
177 Model(&Media{}).
178 Select("path").
179 Where("path_hash = ?", pathHash).
180 Limit(1).
181 Find(&path)
182
183 if result.Error != nil {
184 return "", result.Error
185 }
186
187 return path, nil
188}
189
190func (self *MediaRepository) GetThumbnailPath(ctx context.Context, pathHash string) (string, error) {
191 var path string
192 result := self.db.
193 WithContext(ctx).
194 Model(&Media{}).
195 Select("media_thumbnails.path").
196 Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
197 Where("media.path_hash = ?", pathHash).
198 Limit(1).
199 Find(&path)
200
201 if result.Error != nil {
202 return "", result.Error
203 }
204
205 return path, nil
206}
207
208func (m *MediaRepository) GetEXIF(ctx context.Context, mediaID uint) (*repository.MediaEXIF, error) {
209 exif := &MediaEXIF{}
210 result := m.db.
211 WithContext(ctx).
212 Model(&Media{}).
213 Where("media_id = ?", mediaID).
214 Limit(1).
215 Take(m)
216
217 if result.Error != nil {
218 return nil, result.Error
219 }
220
221 return exif.ToModel(), nil
222}
223
224func (s *MediaRepository) CreateEXIF(ctx context.Context, id uint, info *repository.MediaEXIF) error {
225 media := &MediaEXIF{
226 MediaID: id,
227 Width: info.Width,
228 Height: info.Height,
229 Description: info.Description,
230 Camera: info.Camera,
231 Maker: info.Maker,
232 Lens: info.Lens,
233 DateShot: info.DateShot,
234 Exposure: info.Exposure,
235 Aperture: info.Aperture,
236 Iso: info.Iso,
237 FocalLength: info.FocalLength,
238 Flash: info.Flash,
239 Orientation: info.Orientation,
240 ExposureProgram: info.ExposureProgram,
241 GPSLatitude: info.GPSLatitude,
242 GPSLongitude: info.GPSLongitude,
243 }
244
245 result := s.db.
246 WithContext(ctx).
247 Create(media)
248 if result.Error != nil {
249 return result.Error
250 }
251
252 return nil
253}
254
255func (r *MediaRepository) ListEmptyEXIF(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
256 medias := make([]*Media, 0)
257 result := r.db.
258 WithContext(ctx).
259 Model(&Media{}).
260 Joins("left join media_exifs on media.id = media_exifs.media_id").
261 Where("media_exifs.media_id IS NULL AND media.path like '" + pagination.Path + "%'").
262 Offset(pagination.Page * pagination.Size).
263 Limit(pagination.Size).
264 Order("media.created_at DESC").
265 Find(&medias)
266
267 if result.Error != nil {
268 return nil, result.Error
269 }
270
271 m := list.Map(medias, func(s *Media) *repository.Media {
272 return s.ToModel()
273 })
274
275 return m, nil
276}
277
278func (r *MediaRepository) ListEmptyThumbnail(ctx context.Context, pagination *repository.Pagination) ([]*repository.Media, error) {
279 medias := make([]*Media, 0)
280 result := r.db.
281 WithContext(ctx).
282 Model(&Media{}).
283 Joins("left join media_thumbnails on media.id = media_thumbnails.media_id").
284 Where("media_thumbnails.media_id IS NULL AND media.path like '" + pagination.Path + "%'").
285 Offset(pagination.Page * pagination.Size).
286 Limit(pagination.Size).
287 Order("media.created_at DESC").
288 Find(&medias)
289
290 if result.Error != nil {
291 return nil, result.Error
292 }
293
294 m := list.Map(medias, func(s *Media) *repository.Media {
295 return s.ToModel()
296 })
297
298 return m, nil
299}
300
301func (m *MediaRepository) GetThumbnail(ctx context.Context, mediaID uint) (*repository.MediaThumbnail, error) {
302 thumbnail := &MediaThumbnail{}
303 result := m.db.
304 WithContext(ctx).
305 Model(&Media{}).
306 Where("media_id = ?", mediaID).
307 Limit(1).
308 Take(m)
309
310 if result.Error != nil {
311 return nil, result.Error
312 }
313
314 return thumbnail.ToModel(), nil
315}
316
317func (m *MediaRepository) CreateThumbnail(ctx context.Context, mediaID uint, thumbnail *repository.MediaThumbnail) error {
318 media := &MediaThumbnail{
319 MediaID: mediaID,
320 Path: thumbnail.Path,
321 }
322
323 result := m.db.
324 WithContext(ctx).
325 Create(media)
326 if result.Error != nil {
327 return result.Error
328 }
329
330 return nil
331}