allow for empty strings in MPD::Song::SetTag()
[ncmpcpp.git] / src / song.cpp
blobef75ecd62624bddcd261f1e24d30855828532aff
1 /***************************************************************************
2 * Copyright (C) 2008-2009 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include <cstring>
26 #include <iomanip>
27 #include <sstream>
28 #include <stdexcept>
30 #include "charset.h"
31 #include "conv.h"
32 #include "error.h"
33 #include "song.h"
35 namespace
37 unsigned calc_hash(const char *p)
39 unsigned hash = 5381;
40 while (*p)
41 hash = (hash << 5) + hash + *p++;
42 return hash;
46 MPD::Song::Song(mpd_song *s, bool copy_ptr) : itsSong(s),
47 itsFile(0),
48 itsTags(0),
49 itsSlash(std::string::npos),
50 itsHash(0),
51 copyPtr(copy_ptr),
52 isLocalised(0)
54 if (itsSong)
55 SetHashAndSlash();
58 MPD::Song::Song(const Song &s) : itsSong(s.copyPtr ? s.itsSong : mpd_song_dup(s.itsSong)),
59 itsFile(s.itsFile ? strdup(s.itsFile) : 0),
60 itsTags(s.itsTags ? new TagMap(*s.itsTags) : 0),
61 itsNewName(s.itsNewName),
62 itsSlash(s.itsSlash),
63 itsHash(s.itsHash),
64 copyPtr(s.copyPtr),
65 isLocalised(s.isLocalised)
69 MPD::Song::~Song()
71 if (itsSong)
72 mpd_song_free(itsSong);
73 delete [] itsFile;
74 delete itsTags;
77 std::string MPD::Song::GetLength() const
79 unsigned len = mpd_song_get_duration(itsSong);
80 return !len ? "-:--" : ShowTime(len);
83 void MPD::Song::Localize()
85 # ifdef HAVE_ICONV_H
86 if (isLocalised)
87 return;
88 const char *tag, *conv_tag;
89 conv_tag = tag = mpd_song_get_uri(itsSong);
90 utf_to_locale(conv_tag, 0);
91 if (tag != conv_tag) // file has been converted
93 itsFile = conv_tag;
94 SetHashAndSlash();
96 for (unsigned t = MPD_TAG_ARTIST; t <= MPD_TAG_DISC; ++t)
98 unsigned pos = 0;
99 for (; (tag = mpd_song_get_tag(itsSong, mpd_tag_type(t), pos)); ++pos)
101 conv_tag = tag;
102 utf_to_locale(conv_tag, 0);
103 if (tag != conv_tag) // tag has been converted
105 SetTag(mpd_tag_type(t), pos, conv_tag);
106 delete [] conv_tag;
110 isLocalised = 1;
111 # endif // HAVE_ICONV_H
114 void MPD::Song::Clear()
116 if (itsSong)
117 mpd_song_free(itsSong);
118 itsSong = 0;
120 delete [] itsFile;
121 itsFile = 0;
123 delete itsTags;
124 itsTags = 0;
126 itsNewName.clear();
127 itsSlash = std::string::npos;
128 itsHash = 0;
129 isLocalised = 0;
130 copyPtr = 0;
133 bool MPD::Song::Empty() const
135 return !itsSong;
138 bool MPD::Song::isFromDB() const
140 return (MyFilename()[0] != '/') || itsSlash == std::string::npos;
143 bool MPD::Song::isStream() const
145 return !strncmp(MyFilename(), "http://", 7);
148 std::string MPD::Song::GetFile() const
150 return MyFilename();
153 std::string MPD::Song::GetName() const
155 std::string name = GetTag(MPD_TAG_NAME, 0);
156 if (!name.empty())
157 return name;
158 else if (itsSlash != std::string::npos)
159 return MyFilename()+itsSlash+1;
160 else
161 return MyFilename();
164 std::string MPD::Song::GetDirectory() const
166 if (isStream())
167 return "";
168 else if (itsSlash == std::string::npos)
169 return "/";
170 else
171 return std::string(MyFilename(), itsSlash);
174 std::string MPD::Song::GetArtist() const
176 return GetTag(MPD_TAG_ARTIST, 0);
179 std::string MPD::Song::GetTitle() const
181 return GetTag(MPD_TAG_TITLE, 0);
184 std::string MPD::Song::GetAlbum() const
186 return GetTag(MPD_TAG_ALBUM, 0);
189 std::string MPD::Song::GetTrack() const
191 std::string track = GetTag(MPD_TAG_TRACK, 0);
192 return track.length() == 1 && track[0] != '0' ? "0"+track : track;
195 std::string MPD::Song::GetTrackNumber() const
197 std::string track = GetTag(MPD_TAG_TRACK, 0);
198 size_t slash = track.find('/');
199 if (slash != std::string::npos)
201 track = track.substr(slash+1);
202 return track.length() == 1 && track[0] != '0' ? "0"+track : track;
204 else
205 return track;
208 std::string MPD::Song::GetDate() const
210 return GetTag(MPD_TAG_DATE, 0);
213 std::string MPD::Song::GetGenre() const
215 return GetTag(MPD_TAG_GENRE, 0);
218 std::string MPD::Song::GetComposer() const
220 return GetTag(MPD_TAG_COMPOSER, 0);
223 std::string MPD::Song::GetPerformer() const
225 return GetTag(MPD_TAG_PERFORMER, 0);
228 std::string MPD::Song::GetDisc() const
230 return GetTag(MPD_TAG_DISC, 0);
233 std::string MPD::Song::GetComment() const
235 return GetTag(MPD_TAG_COMMENT, 0);
238 void MPD::Song::SetArtist(const std::string &str)
240 SetTag(MPD_TAG_ARTIST, 0, str);
243 void MPD::Song::SetTitle(const std::string &str)
245 SetTag(MPD_TAG_TITLE, 0, str);
248 void MPD::Song::SetAlbum(const std::string &str)
250 SetTag(MPD_TAG_ALBUM, 0, str);
253 void MPD::Song::SetTrack(const std::string &str)
255 SetTag(MPD_TAG_TRACK, 0, str);
258 void MPD::Song::SetTrack(unsigned track)
260 SetTag(MPD_TAG_ARTIST, 0, IntoStr(track));
263 void MPD::Song::SetDate(const std::string &str)
265 SetTag(MPD_TAG_DATE, 0, str);
268 void MPD::Song::SetDate(unsigned year)
270 SetTag(MPD_TAG_TRACK, 0, IntoStr(year));
273 void MPD::Song::SetGenre(const std::string &str)
275 SetTag(MPD_TAG_GENRE, 0, str);
278 void MPD::Song::SetComposer(const std::string &str)
280 SetTag(MPD_TAG_COMPOSER, 0, str);
283 void MPD::Song::SetPerformer(const std::string &str)
285 SetTag(MPD_TAG_PERFORMER, 0, str);
288 void MPD::Song::SetDisc(const std::string &str)
290 SetTag(MPD_TAG_DISC, 0, str);
293 void MPD::Song::SetComment(const std::string &str)
295 SetTag(MPD_TAG_COMMENT, 0, str);
298 void MPD::Song::SetPosition(unsigned pos)
300 mpd_song_set_pos(itsSong, pos);
303 std::string MPD::Song::ParseFormat(std::string::const_iterator &it, const char *escape_chars) const
305 std::string result;
306 bool has_some_tags = 0;
307 MPD::Song::GetFunction get = 0;
308 while (*++it != '}')
310 while (*it == '{')
312 std::string tags = ParseFormat(it, escape_chars);
313 if (!tags.empty())
315 has_some_tags = 1;
316 result += tags;
319 if (*it == '}')
320 break;
322 if (*it == '%')
324 switch (*++it)
326 case 'l':
327 get = &MPD::Song::GetLength;
328 break;
329 case 'D':
330 get = &MPD::Song::GetDirectory;
331 break;
332 case 'f':
333 get = &MPD::Song::GetName;
334 break;
335 case 'a':
336 get = &MPD::Song::GetArtist;
337 break;
338 case 'b':
339 get = &MPD::Song::GetAlbum;
340 break;
341 case 'y':
342 get = &MPD::Song::GetDate;
343 break;
344 case 'n':
345 get = &MPD::Song::GetTrackNumber;
346 break;
347 case 'N':
348 get = &MPD::Song::GetTrack;
349 break;
350 case 'g':
351 get = &MPD::Song::GetGenre;
352 break;
353 case 'c':
354 get = &MPD::Song::GetComposer;
355 break;
356 case 'p':
357 get = &MPD::Song::GetPerformer;
358 break;
359 case 'd':
360 get = &MPD::Song::GetDisc;
361 break;
362 case 'C':
363 get = &MPD::Song::GetComment;
364 break;
365 case 't':
366 get = &MPD::Song::GetTitle;
367 break;
368 case '%':
369 result += *it; // no break here
370 default:
371 get = 0;
372 break;
374 if (get)
376 std::string tag = (this->*get)();
377 if (escape_chars) // prepend format escape character to all given chars to escape
378 for (const char *ch = escape_chars; *ch; ++ch)
379 for (size_t i = tag.find(*ch); i != std::string::npos; i = tag.find(*ch, i += 2))
380 tag.replace(i, 1, std::string(1, FormatEscapeCharacter) + ch);
381 if (!tag.empty() && (get != &MPD::Song::GetLength || GetTotalLength()))
383 has_some_tags = 1;
384 result += tag;
386 else
387 break;
390 else
391 result += *it;
393 int brace_counter = 0;
394 if (*it != '}' || !has_some_tags)
396 for (; *it != '}' || brace_counter; ++it)
398 if (*it == '{')
399 ++brace_counter;
400 else if (*it == '}')
401 --brace_counter;
403 if (*++it == '|')
404 return ParseFormat(++it, escape_chars);
405 else
406 return "";
408 else
410 if (*(it+1) == '|')
412 for (; *it != '}' || *(it+1) == '|' || brace_counter; ++it)
414 if (*it == '{')
415 ++brace_counter;
416 else if (*it == '}')
417 --brace_counter;
420 ++it;
421 return result;
425 std::string MPD::Song::toString(const std::string &format, const char *escape_chars) const
427 std::string::const_iterator it = format.begin();
428 return ParseFormat(it, escape_chars);
431 MPD::Song &MPD::Song::operator=(const MPD::Song &s)
433 if (this == &s)
434 return *this;
435 if (itsSong)
436 mpd_song_free(itsSong);
437 delete [] itsFile;
438 delete itsTags;
439 itsSong = s.copyPtr ? s.itsSong : (s.itsSong ? mpd_song_dup(s.itsSong) : 0);
440 itsFile = s.itsFile ? strdup(s.itsFile) : 0;
441 itsTags = s.itsTags ? new TagMap(*s.itsTags) : 0;
442 itsNewName = s.itsNewName;
443 itsSlash = s.itsSlash;
444 itsHash = s.itsHash;
445 copyPtr = s.copyPtr;
446 isLocalised = s.isLocalised;
447 return *this;
450 std::string MPD::Song::ShowTime(int length)
452 std::ostringstream ss;
454 int hours = length/3600;
455 length -= hours*3600;
456 int minutes = length/60;
457 length -= minutes*60;
458 int seconds = length;
460 if (hours > 0)
462 ss << hours << ":"
463 << std::setw(2) << std::setfill('0') << minutes << ":"
464 << std::setw(2) << std::setfill('0') << seconds;
466 else
468 ss << minutes << ":"
469 << std::setw(2) << std::setfill('0') << seconds;
471 return ss.str();
474 void MPD::Song::ValidateFormat(const std::string &type, const std::string &s)
476 int braces = 0;
477 for (std::string::const_iterator it = s.begin(); it != s.end(); ++it)
479 if (*it == '{')
480 ++braces;
481 else if (*it == '}')
482 --braces;
484 if (braces)
485 FatalError(type + ": number of opening and closing braces does not equal!");
488 void MPD::Song::SetHashAndSlash()
490 const char *filename = MyFilename();
491 if (!isStream())
493 const char *tmp = strrchr(filename, '/');
494 itsSlash = tmp ? tmp-filename : std::string::npos;
496 if (!itsFile)
497 itsHash = calc_hash(filename);
500 const char *MPD::Song::MyFilename() const
502 return itsFile ? itsFile : mpd_song_get_uri(itsSong);
505 void MPD::Song::SetTag(mpd_tag_type type, unsigned pos, const std::string &value)
507 if (!itsTags)
508 itsTags = new TagMap;
509 (*itsTags)[std::make_pair(type, pos)] = value;
512 std::string MPD::Song::GetTag(mpd_tag_type type, unsigned pos) const
514 if (itsTags)
516 TagMap::const_iterator it = itsTags->find(std::make_pair(type, pos));
517 if (it != itsTags->end())
518 return it->second;
520 const char *tag = mpd_song_get_tag(itsSong, type, pos);
521 return tag ? tag : "";