1 #include <cstring>
2 #include <cstdio>
3 #include <stdexcept>
4 #include <vector>
5 #include <string>
6 #include <algorithm>
7 #include "charconv.h"
8 #include "sedit.h"
9 #include "set_base.h"
10 #include "mass_tag.h"
11
12 /*
13
14 copyright (c) 2004-2006, 2015 squell <squell@alumina.nl>
15
16 use, modification, copying and distribution of this software is permitted
17 under the conditions described in the file 'COPYING'.
18
19 */
20
21 using namespace std;
22 using namespace charset;
23 using tag::ID3field;
24
25 namespace fileexp {
26
27 // defining variable mapping characters <-> ID3field
28
field(wchar_t c)29 ID3field mass_tag::field(wchar_t c)
30 {
31 switch(c) {
32 case 't': return tag::title;
33 case 'a': return tag::artist;
34 case 'l': return tag::album;
35 case 'y': return tag::year;
36 case 'c': return tag::cmnt;
37 case 'n': return tag::track;
38 case 'g': return tag::genre;
39 case 'A': return tag::album; // compat.
40 case 'T': return tag::track;
41 default : return tag::FIELD_MAX;
42 }
43 }
44
var(int i)45 string mass_tag::var(int i)
46 {
47 const char tab[] = "talycng";
48 if(i < tag::FIELD_MAX)
49 return string(1,'%') += tab[i];
50 else
51 return string();
52 }
53
54 namespace {
55
56 // variable mapping for substitution
57 // - only reads tag data from file when actually requested
58
59 unsigned long numfiles = 0;
60
61 class substvars : public stredit::format {
62 public:
63 virtual result var(ptr& p, ptr) const;
64
substvars(const char * fn,const fileexp::record & rec,const tag::reader & info,unsigned long & x)65 substvars(const char* fn,
66 const fileexp::record& rec,
67 const tag::reader& info,
68 unsigned long& x)
69 : tag_data(info.read(rec.path)), num(0),
70 filename(fn), filerec(&rec), cnt(&x) { }
~substvars()71 ~substvars()
72 { delete tag_data; }
73
74 private:
75 const tag::metadata* const tag_data;
76 mutable unsigned long int num;
77
78 const char* const filename;
79 const fileexp::record* const filerec;
80 unsigned long int* const cnt;
81
82 substvars(const substvars&); // don't copy
83 };
84
var(ptr & p,ptr end) const85 substvars::result substvars::var(ptr& p, ptr end) const
86 {
87 static const result empty(conv<latin1>("<empty>"), false);
88 switch(wchar_t c = *p++) {
89 case '?':
90 return false;
91 case 'x':
92 if(!num) num = (*cnt)++;
93 char buf[11];
94 int n; n = sprintf(buf, "%lu", num & 0xFFFFu); // to jump past
95 return conv<latin1>(buf, n>0? n : 0);
96 case 'X':
97 n = sprintf(buf, "%lu", numfiles & 0xFFFFu);
98 return conv<latin1>(buf, n>0? n : 0);
99 case 'p':
100 return conv<local>(filerec->path, filename - filerec->path);
101 case 'f':
102 return conv<local>(filename);
103 case '0': c += 10;
104 case '1':
105 case '2':
106 case '3':
107 case '4':
108 case '5':
109 case '6':
110 case '7':
111 case '8':
112 case '9':
113 if(c-'1' >= filerec->var.size()) {
114 static const char error[] = "variable index out of range: %";
115 throw out_of_range(error+std::string(1,c));
116 }
117 return conv<local>(filerec->var[c-'1']);
118 case '{': {
119 ptr q = std::find(p, end, '}');
120 if(q == end) {
121 throw out_of_range("missing } in variable");
122 } else if(q == p+1) {
123 const result& tmp = var(p, end);
124 return ++p, tmp;
125 }
126 const string key = conv<wchar_t>(wstring(p, q)).str<local>();
127 p = q+1;
128
129 typedef tag::metadata::array info;
130 const info frames = tag_data->listing();
131 for(info::const_iterator rec = frames.begin(); rec != frames.end(); rec++) {
132 // not entirely strict; FOO:bar will match FOO:bar:hguk
133 if(key == rec->first.substr(0,rec->first.find(':', key.length())))
134 return rec->second;
135 }
136 return conv<>();
137 }
138
139 default:
140 ID3field i; i = mass_tag::field(c);
141 if(i >= tag::FIELD_MAX) {
142 static const char error[] = "unknown variable: %";
143 throw out_of_range(error+conv<wchar_t>(&c, 1).str<local>());
144 }
145 const result& tmp = (*tag_data)[i];
146 return tmp.empty()? empty : tmp;
147 };
148 }
149
150 }
151
total()152 unsigned long int mass_tag::total()
153 {
154 return numfiles;
155 }
156
157 // implementation of fileexp::find using tag objects
158
dir(const fileexp::record & d)159 bool mass_tag::dir(const fileexp::record& d)
160 {
161 return (counter = 1);
162 }
163
file(const char * name,const fileexp::record & f)164 bool mass_tag::file(const char* name, const fileexp::record& f)
165 {
166 substvars vars(name, f, tag_reader, counter);
167 ++numfiles;
168
169 return tag_writer.modify(f.path, vars);
170 }
171
172 } // namespace
173
174