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