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, write_comments, comment_sort, max_comments, comment_extractor_args, 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 boolToInt(p.WriteComments), p.CommentSort, p.MaxComments, p.CommentExtractorArgs,
32 p.CustomFlags,
33 )
34 if err != nil {
35 return err
36 }
37 id, err := result.LastInsertId()
38 if err != nil {
39 return fmt.Errorf("preset last insert id: %w", err)
40 }
41 p.ID = id
42 return nil
43}
44
45func updatePreset(e execer, p *models.Preset) error {
46 _, err := e.Exec(
47 `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=?, write_comments=?, comment_sort=?, max_comments=?, comment_extractor_args=?, custom_flags=?
48 WHERE id=?`,
49 p.Name, p.Description, boolToInt(p.IsDefault), p.FormatMode, p.Format, p.Quality, p.CustomFormat,
50 boolToInt(p.ExtractAudio), p.AudioFormat, boolToInt(p.EmbedSubs), p.SubLangs,
51 boolToInt(p.EmbedThumbnail), boolToInt(p.EmbedMetadata), boolToInt(p.WriteInfoJSON),
52 boolToInt(p.WriteComments), p.CommentSort, p.MaxComments, p.CommentExtractorArgs,
53 p.CustomFlags, p.ID,
54 )
55 return err
56}
57
58func clearDefault(e execer) error {
59 _, err := e.Exec(`UPDATE presets SET is_default = 0`)
60 return err
61}
62
63// Save persists p, inserting it when it has no id yet and updating it otherwise.
64// When p is the new default it first clears the previous default, doing all of it
65// within a single transaction so a mid-sequence failure can't leave the library
66// with no default set. Create and Update both route through here, so the
67// exclusive-default invariant can't be bypassed.
68func (r *PresetRepository) Save(p *models.Preset) error {
69 tx, err := r.db.Begin()
70 if err != nil {
71 return err
72 }
73 defer tx.Rollback()
74
75 if p.IsDefault {
76 if err := clearDefault(tx); err != nil {
77 return err
78 }
79 }
80 if p.ID == 0 {
81 err = insertPreset(tx, p)
82 } else {
83 err = updatePreset(tx, p)
84 }
85 if err != nil {
86 return err
87 }
88 return tx.Commit()
89}
90
91func (r *PresetRepository) GetByID(id int64) (*models.Preset, error) {
92 row := r.db.QueryRow(
93 `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, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags, created_at
94 FROM presets WHERE id = ?`, id,
95 )
96 return scanPreset(row)
97}
98
99func (r *PresetRepository) GetAll() ([]*models.Preset, error) {
100 rows, err := r.db.Query(
101 `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, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags, created_at
102 FROM presets ORDER BY name`,
103 )
104 if err != nil {
105 return nil, err
106 }
107 defer rows.Close()
108
109 var presets []*models.Preset
110 for rows.Next() {
111 p, err := scanPreset(rows)
112 if err != nil {
113 return nil, err
114 }
115 presets = append(presets, p)
116 }
117 return presets, rows.Err()
118}
119
120func (r *PresetRepository) GetDefault() (*models.Preset, error) {
121 row := r.db.QueryRow(
122 `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, write_comments, comment_sort, max_comments, comment_extractor_args, custom_flags, created_at
123 FROM presets WHERE is_default = 1 LIMIT 1`,
124 )
125 return scanPreset(row)
126}
127
128func (r *PresetRepository) Delete(id int64) error {
129 _, err := r.db.Exec(`DELETE FROM presets WHERE id = ?`, id)
130 return err
131}
132
133func scanPreset(row interface{ Scan(...interface{}) error }) (*models.Preset, error) {
134 var p models.Preset
135 var isDefault, extractAudio, embedSubs, embedThumbnail, embedMetadata, writeInfoJSON, writeComments, maxComments int
136 var description, formatMode, format, quality, customFormat, audioFormat, subLangs, commentSort, commentExtractorArgs, customFlags sql.NullString
137 err := row.Scan(
138 &p.ID, &p.Name, &description, &isDefault, &formatMode, &format, &quality, &customFormat,
139 &extractAudio, &audioFormat, &embedSubs, &subLangs,
140 &embedThumbnail, &embedMetadata, &writeInfoJSON,
141 &writeComments, &commentSort, &maxComments, &commentExtractorArgs,
142 &customFlags, &p.CreatedAt,
143 )
144 if err != nil {
145 return nil, err
146 }
147 p.Description = description.String
148 p.FormatMode = formatMode.String
149 p.Format = format.String
150 p.Quality = quality.String
151 p.CustomFormat = customFormat.String
152 p.AudioFormat = audioFormat.String
153 p.SubLangs = subLangs.String
154 p.CustomFlags = customFlags.String
155 p.CommentSort = commentSort.String
156 p.CommentExtractorArgs = commentExtractorArgs.String
157 p.MaxComments = maxComments
158 p.IsDefault = isDefault == 1
159 p.ExtractAudio = extractAudio == 1
160 p.EmbedSubs = embedSubs == 1
161 p.EmbedThumbnail = embedThumbnail == 1
162 p.EmbedMetadata = embedMetadata == 1
163 p.WriteInfoJSON = writeInfoJSON == 1
164 p.WriteComments = writeComments == 1
165 return &p, nil
166}
167
168func boolToInt(b bool) int {
169 if b {
170 return 1
171 }
172 return 0
173}
174