1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#include "song.hpp"
#include <esp_log.h>
#include <ff.h>
#include <komihash.h>
#include <tags.h>
namespace database {
namespace libtags {
struct Aux {
FIL file;
FILINFO info;
SongTags* tags;
};
static int read(Tagctx* ctx, void* buf, int cnt) {
Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
UINT bytes_read;
if (f_read(&aux->file, buf, cnt, &bytes_read) != FR_OK) {
return -1;
}
return bytes_read;
}
static int seek(Tagctx* ctx, int offset, int whence) {
Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
FRESULT res;
if (whence == 0) {
// Seek from the start of the file. This is f_lseek's behaviour.
res = f_lseek(&aux->file, offset);
} else if (whence == 1) {
// Seek from current offset.
res = f_lseek(&aux->file, aux->file.fptr + offset);
} else if (whence == 2) {
// Seek from the end of the file
res = f_lseek(&aux->file, aux->info.fsize + offset);
} else {
return -1;
}
return res;
}
static void tag(Tagctx* ctx,
int t,
const char* k,
const char* v,
int offset,
int size,
Tagread f) {
Aux* aux = reinterpret_cast<Aux*>(ctx->aux);
if (t == Ttitle) {
aux->tags->title = v;
} else if (t == Tartist) {
aux->tags->artist = v;
} else if (t == Talbum) {
aux->tags->album = v;
}
}
static void toc(Tagctx* ctx, int ms, int offset) {}
} // namespace libtags
static const std::size_t kBufSize = 1024;
static const char* kTag = "TAGS";
auto ReadAndParseTags(const std::string& path, SongTags* out) -> bool {
libtags::Aux aux;
aux.tags = out;
if (f_stat(path.c_str(), &aux.info) != FR_OK ||
f_open(&aux.file, path.c_str(), FA_READ) != FR_OK) {
ESP_LOGW(kTag, "failed to open file %s", path.c_str());
return false;
}
// Fine to have this on the stack; this is only called on the leveldb task.
char buf[kBufSize];
Tagctx ctx;
ctx.read = libtags::read;
ctx.seek = libtags::seek;
ctx.tag = libtags::tag;
ctx.toc = libtags::toc;
ctx.aux = &aux;
ctx.buf = buf;
ctx.bufsz = kBufSize;
int res = tagsget(&ctx);
f_close(&aux.file);
if (res != 0) {
// Parsing failed.
return false;
}
switch (ctx.format) {
case Fmp3:
out->encoding = ENC_MP3;
break;
default:
out->encoding = ENC_UNSUPPORTED;
}
return true;
}
auto HashString(komihash_stream_t* stream, std::string str) -> void {
komihash_stream_update(stream, str.c_str(), str.length());
}
auto SongTags::Hash() const -> uint64_t {
komihash_stream_t stream;
komihash_stream_init(&stream, 0);
HashString(&stream, title.value_or(""));
HashString(&stream, artist.value_or(""));
HashString(&stream, album.value_or(""));
return komihash_stream_final(&stream);
}
auto SongData::UpdateHash(uint64_t new_hash) const -> SongData {
return SongData(id_, filepath_, new_hash, play_count_, is_tombstoned_);
}
auto SongData::Entomb() const -> SongData {
return SongData(id_, filepath_, tags_hash_, play_count_, true);
}
auto SongData::Exhume(const std::string& new_path) const -> SongData {
return SongData(id_, new_path, tags_hash_, play_count_, false);
}
} // namespace database
|