preset.go
⎇
Raw
1package repository
2
3import (
4 "database/sql"
5 "fmt"
6
7 "vidarchive/internal/models"
8)
9
10// execer is satisfied by both *sql.DB and *sql.Tx, so the insert/update/clear
11// helpers can run either directly or inside a transaction (see Save).
12type execer interface {
13 Exec(query string, args ...interface{}) (sql.Result, error)
14}
15
16type PresetRepository struct {
17 db *sql.DB
18}
19
20func NewPresetRepository(db *sql.DB) *PresetRepository {
21 return &PresetRepository{db: db}
22}
23
24func insertPreset(e execer, p *models.Preset) error {
25 result, err := e.Exec(
26 `INSERT INTO presets (name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, custom_flags)
27 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
28 p.Name, p.Description, boolToInt(p.IsDefault), p.FormatMode, p.Format, p.Quality, p.CustomFormat,
29 boolToInt(p.ExtractAudio), p.AudioFormat, boolToInt(p.EmbedSubs), p.SubLangs,
30 boolToInt(p.EmbedThumbnail), boolToInt(p.EmbedMetadata), boolToInt(p.WriteInfoJSON),
31 p.CustomFlags,
32 )
33 if err != nil {
34 return err
35 }
36 id, err := result.LastInsertId()
37 if err != nil {
38 return fmt.Errorf("preset last insert id: %w", err)
39 }
40 p.ID = id
41 return nil
42}
43
44func updatePreset(e execer, p *models.Preset) error {
45 _, err := e.Exec(
46 `UPDATE presets SET name=?, description=?, is_default=?, format_mode=?, format=?, quality=?, custom_format=?, extract_audio=?, audio_format=?, embed_subs=?, sub_langs=?, embed_thumbnail=?, embed_metadata=?, write_info_json=?, custom_flags=?
47 WHERE id=?`,
48 p.Name, p.Description, boolToInt(p.IsDefault), p.FormatMode, p.Format, p.Quality, p.CustomFormat,
49 boolToInt(p.ExtractAudio), p.AudioFormat, boolToInt(p.EmbedSubs), p.SubLangs,
50 boolToInt(p.EmbedThumbnail), boolToInt(p.EmbedMetadata), boolToInt(p.WriteInfoJSON),
51 p.CustomFlags, p.ID,
52 )
53 return err
54}
55
56func clearDefault(e execer) error {
57 _, err := e.Exec(`UPDATE presets SET is_default = 0`)
58 return err
59}
60
61// Save persists p, inserting it when it has no id yet and updating it otherwise.
62// When p is the new default it first clears the previous default, doing all of it
63// within a single transaction so a mid-sequence failure can't leave the library
64// with no default set. Create and Update both route through here, so the
65// exclusive-default invariant can't be bypassed.
66func (r *PresetRepository) Save(p *models.Preset) error {
67 tx, err := r.db.Begin()
68 if err != nil {
69 return err
70 }
71 defer tx.Rollback()
72
73 if p.IsDefault {
74 if err := clearDefault(tx); err != nil {
75 return err
76 }
77 }
78 if p.ID == 0 {
79 err = insertPreset(tx, p)
80 } else {
81 err = updatePreset(tx, p)
82 }
83 if err != nil {
84 return err
85 }
86 return tx.Commit()
87}
88
89func (r *PresetRepository) GetByID(id int64) (*models.Preset, error) {
90 row := r.db.QueryRow(
91 `SELECT id, name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, custom_flags, created_at
92 FROM presets WHERE id = ?`, id,
93 )
94 return scanPreset(row)
95}
96
97func (r *PresetRepository) GetAll() ([]*models.Preset, error) {
98 rows, err := r.db.Query(
99 `SELECT id, name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, custom_flags, created_at
100 FROM presets ORDER BY name`,
101 )
102 if err != nil {
103 return nil, err
104 }
105 defer rows.Close()
106
107 var presets []*models.Preset
108 for rows.Next() {
109 p, err := scanPreset(rows)
110 if err != nil {
111 return nil, err
112 }
113 presets = append(presets, p)
114 }
115 return presets, rows.Err()
116}
117
118func (r *PresetRepository) GetDefault() (*models.Preset, error) {
119 row := r.db.QueryRow(
120 `SELECT id, name, description, is_default, format_mode, format, quality, custom_format, extract_audio, audio_format, embed_subs, sub_langs, embed_thumbnail, embed_metadata, write_info_json, custom_flags, created_at
121 FROM presets WHERE is_default = 1 LIMIT 1`,
122 )
123 return scanPreset(row)
124}
125
126func (r *PresetRepository) Delete(id int64) error {
127 _, err := r.db.Exec(`DELETE FROM presets WHERE id = ?`, id)
128 return err
129}
130
131func scanPreset(row interface{ Scan(...interface{}) error }) (*models.Preset, error) {
132 var p models.Preset
133 var isDefault, extractAudio, embedSubs, embedThumbnail, embedMetadata, writeInfoJSON int
134 var description, formatMode, format, quality, customFormat, audioFormat, subLangs, customFlags sql.NullString
135 err := row.Scan(
136 &p.ID, &p.Name, &description, &isDefault, &formatMode, &format, &quality, &customFormat,
137 &extractAudio, &audioFormat, &embedSubs, &subLangs,
138 &embedThumbnail, &embedMetadata, &writeInfoJSON,
139 &customFlags, &p.CreatedAt,
140 )
141 if err != nil {
142 return nil, err
143 }
144 p.Description = description.String
145 p.FormatMode = formatMode.String
146 p.Format = format.String
147 p.Quality = quality.String
148 p.CustomFormat = customFormat.String
149 p.AudioFormat = audioFormat.String
150 p.SubLangs = subLangs.String
151 p.CustomFlags = customFlags.String
152 p.IsDefault = isDefault == 1
153 p.ExtractAudio = extractAudio == 1
154 p.EmbedSubs = embedSubs == 1
155 p.EmbedThumbnail = embedThumbnail == 1
156 p.EmbedMetadata = embedMetadata == 1
157 p.WriteInfoJSON = writeInfoJSON == 1
158 return &p, nil
159}
160
161func boolToInt(b bool) int {
162 if b {
163 return 1
164 }
165 return 0
166}
167