Skip to content

Commit 97e0601

Browse files
committed
support tag diff before saving
1 parent ec7ed7a commit 97e0601

File tree

5 files changed

+74
-50
lines changed

5 files changed

+74
-50
lines changed

README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,29 @@ func main() {
4343

4444
// Non-standard allowed too
4545
"ALBUMARTIST_CREDIT": {"Brian Eno & David Bynre"},
46-
})
46+
}, 0)
4747
// check(err)
4848
}
4949
```
5050

51+
#### Options for writing
52+
53+
The behaviour of writing can be configured with some bitset flags
54+
55+
The options are
56+
57+
- `Clear` which clears existing metadata before writing the new
58+
- `DiffBeforeWrite` which won't modify the file on disk if the new metadata is the same as the old
59+
60+
The options can be combined the with the bitwise `OR` operator (`|`)
61+
62+
```go
63+
taglib.WriteTags(path, tags, taglib.Clear|taglib.DiffBeforeWrite) // clear and diff
64+
taglib.WriteTags(path, tags, taglib.DiffBeforeWrite) // only diff diff
65+
taglib.WriteTags(path, tags, taglib.Clear) // only clear
66+
taglib.WriteTags(path, tags, 0) // don't clear or diff
67+
```
68+
5169
### Reading properties
5270

5371
```go

taglib.cpp

+19-20
Original file line numberDiff line numberDiff line change
@@ -46,41 +46,40 @@ taglib_file_tags(const char *filename) {
4646
return tags;
4747
}
4848

49+
static const uint8_t CLEAR = 1 << 0;
50+
static const uint8_t DIFF_SAVE = 1 << 1;
51+
4952
__attribute__((export_name("taglib_file_write_tags"))) bool
50-
taglib_file_write_tags(const char *filename, const char **tags, bool replace) {
53+
taglib_file_write_tags(const char *filename, const char **tags, uint8_t opts) {
5154
if (!filename || !tags)
5255
return false;
5356

5457
TagLib::FileRef file(filename);
5558
if (file.isNull())
5659
return false;
5760

58-
TagLib::PropertyMap properties;
59-
for (size_t i = 0; tags[i] != nullptr; i++) {
61+
auto properties = file.properties();
62+
if (opts & CLEAR)
63+
properties.clear();
64+
65+
for (size_t i = 0; tags[i]; i++) {
6066
TagLib::String row(tags[i], TagLib::String::UTF8);
6167
if (auto ti = row.find("\t"); ti != -1) {
62-
TagLib::String key(row.substr(0, ti));
63-
if (auto v = row.substr(ti + 1); !v.isEmpty()) {
64-
properties.insert(key, TagLib::StringList(v));
65-
} else {
66-
properties.insert(key, TagLib::StringList());
67-
}
68+
auto key = row.substr(0, ti);
69+
auto value = row.substr(ti + 1);
70+
if (value.isEmpty())
71+
properties.erase(key);
72+
else
73+
properties.replace(key, value.split("\v"));
6874
}
6975
}
7076

71-
if (replace) {
72-
if (auto rejected = file.setProperties(properties); !rejected.isEmpty())
73-
return 0;
74-
return file.save();
77+
if (opts & DIFF_SAVE) {
78+
if (file.properties() == properties)
79+
return true;
7580
}
7681

77-
auto existing = file.properties();
78-
existing.erase(properties); // wipe the keys we're sending
79-
existing.merge(properties); // merge them in
80-
existing.removeEmpty();
81-
82-
if (auto rejected = file.setProperties(existing); !rejected.isEmpty())
83-
return 0;
82+
file.setProperties(properties);
8483
return file.save();
8584
}
8685

taglib.go

+18-17
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,20 @@ func ReadProperties(path string) (Properties, error) {
221221
}, nil
222222
}
223223

224-
// WriteTags writes the metadata key-values pairs to path, leaving other found keys untouched.
225-
func WriteTags(path string, tags map[string][]string) error {
226-
return writeTagsOpt(path, tags, false)
227-
}
224+
// WriteOption configures the behavior of write operations. The can be passed to [WriteTags] and combined with the bitwise OR operator.
225+
type WriteOption uint8
228226

229-
// ReplaceTags replaces all tag metadata at path with the the metadata in thhe map.
230-
func ReplaceTags(path string, tags map[string][]string) error {
231-
return writeTagsOpt(path, tags, true)
232-
}
227+
const (
228+
// Clear indicates that all existing tags not present in the map should be removed.
229+
Clear WriteOption = 1 << iota
230+
231+
// DiffBeforeWrite enables comparison before writing to disk.
232+
// When set, no write occurs if the map contains no changes compared to the existing tags.
233+
DiffBeforeWrite
234+
)
233235

234-
func writeTagsOpt(path string, tags map[string][]string, replace bool) error {
236+
// WriteTags writes the metadata key-values pairs to path. The behavior can be controlled with [WriteOption].
237+
func WriteTags(path string, tags map[string][]string, opts WriteOption) error {
235238
var err error
236239
path, err = filepath.Abs(path)
237240
if err != nil {
@@ -247,17 +250,11 @@ func writeTagsOpt(path string, tags map[string][]string, replace bool) error {
247250

248251
var raw []string
249252
for k, vs := range tags {
250-
if len(vs) == 0 {
251-
raw = append(raw, k+"\t") // allow clearing
252-
continue
253-
}
254-
for _, v := range vs {
255-
raw = append(raw, k+"\t"+v)
256-
}
253+
raw = append(raw, fmt.Sprintf("%s\t%s", k, strings.Join(vs, "\v")))
257254
}
258255

259256
var out bool
260-
if err := mod.call("taglib_file_write_tags", &out, wasmPath(path), raw, replace); err != nil {
257+
if err := mod.call("taglib_file_write_tags", &out, wasmPath(path), raw, opts); err != nil {
261258
return fmt.Errorf("call: %w", err)
262259
}
263260
if !out {
@@ -374,6 +371,10 @@ func (m *module) call(name string, dest any, args ...any) error {
374371
}
375372
case int:
376373
params = append(params, uint64(a))
374+
case uint8:
375+
params = append(params, uint64(a))
376+
case WriteOption:
377+
params = append(params, uint64(a))
377378
case uint32:
378379
params = append(params, uint64(a))
379380
case uint64:

taglib.wasm

70 Bytes
Binary file not shown.

taglib_test.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ func TestClear(t *testing.T) {
3232
for _, path := range paths {
3333
t.Run(filepath.Base(path), func(t *testing.T) {
3434
// set some tags first
35-
err := taglib.ReplaceTags(path, map[string][]string{
35+
err := taglib.WriteTags(path, map[string][]string{
3636
"ARTIST": {"Example A"},
3737
"ALUMARTIST": {"Example"},
38-
})
38+
}, taglib.Clear)
39+
3940
nilErr(t, err)
4041

4142
// then clear
42-
err = taglib.ReplaceTags(path, nil)
43+
err = taglib.WriteTags(path, nil, taglib.Clear)
4344
nilErr(t, err)
4445

4546
got, err := taglib.ReadTags(path)
@@ -94,7 +95,7 @@ func TestReadWrite(t *testing.T) {
9495
for _, path := range paths {
9596
for i, tags := range testTags {
9697
t.Run(fmt.Sprintf("%s_tags_%d", filepath.Base(path), i), func(t *testing.T) {
97-
err := taglib.ReplaceTags(path, tags)
98+
err := taglib.WriteTags(path, tags, taglib.Clear)
9899
nilErr(t, err)
99100

100101
got, err := taglib.ReadTags(path)
@@ -120,12 +121,13 @@ func TestMergeWrite(t *testing.T) {
120121

121122
for _, path := range paths {
122123
t.Run(filepath.Base(path), func(t *testing.T) {
123-
err := taglib.ReplaceTags(path, nil) // clear
124+
err := taglib.WriteTags(path, nil, taglib.Clear)
124125
nilErr(t, err)
125126

126127
err = taglib.WriteTags(path, map[string][]string{
127128
"ONE": {"one"},
128-
})
129+
}, 0)
130+
129131
nilErr(t, err)
130132
cmp(t, path, map[string][]string{
131133
"ONE": {"one"},
@@ -134,7 +136,8 @@ func TestMergeWrite(t *testing.T) {
134136
nilErr(t, err)
135137
err = taglib.WriteTags(path, map[string][]string{
136138
"TWO": {"two", "two!"},
137-
})
139+
}, 0)
140+
138141
nilErr(t, err)
139142
cmp(t, path, map[string][]string{
140143
"ONE": {"one"},
@@ -143,7 +146,8 @@ func TestMergeWrite(t *testing.T) {
143146

144147
err = taglib.WriteTags(path, map[string][]string{
145148
"THREE": {"three"},
146-
})
149+
}, 0)
150+
147151
nilErr(t, err)
148152
cmp(t, path, map[string][]string{
149153
"ONE": {"one"},
@@ -154,7 +158,8 @@ func TestMergeWrite(t *testing.T) {
154158
// change prev
155159
err = taglib.WriteTags(path, map[string][]string{
156160
"ONE": {"one new"},
157-
})
161+
}, 0)
162+
158163
nilErr(t, err)
159164
cmp(t, path, map[string][]string{
160165
"ONE": {"one new"},
@@ -166,7 +171,8 @@ func TestMergeWrite(t *testing.T) {
166171
err = taglib.WriteTags(path, map[string][]string{
167172
"ONE": {},
168173
"THREE": {"three new!"},
169-
})
174+
}, 0)
175+
170176
nilErr(t, err)
171177
cmp(t, path, map[string][]string{
172178
"TWO": {"two", "two!"},
@@ -276,14 +282,14 @@ func BenchmarkWrite(b *testing.B) {
276282
b.ResetTimer()
277283

278284
for range b.N {
279-
err := taglib.ReplaceTags(path, bigTags)
285+
err := taglib.WriteTags(path, bigTags, taglib.Clear)
280286
nilErr(b, err)
281287
}
282288
}
283289

284290
func BenchmarkRead(b *testing.B) {
285291
path := tmpf(b, egFLAC, "eg.flac")
286-
err := taglib.ReplaceTags(path, bigTags)
292+
err := taglib.WriteTags(path, bigTags, taglib.Clear)
287293
nilErr(b, err)
288294
b.ResetTimer()
289295

0 commit comments

Comments
 (0)