plugin: convert plugin_fd_add() to method
[ncmpc-debian.git] / src / strfsong.cxx
index 3272d5f..f2101fb 100644 (file)
@@ -1,5 +1,5 @@
 /* ncmpc (Ncurses MPD Client)
- * (c) 2004-2018 The Music Player Daemon Project
+ * (c) 2004-2019 The Music Player Daemon Project
  * Project homepage: http://musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
 #include "strfsong.hxx"
 #include "charset.hxx"
 #include "time_format.hxx"
+#include "util/UriUtil.hxx"
 
 #include <mpd/client.h>
 
-#include <glib.h>
+#include <algorithm>
 
 #include <string.h>
 
+gcc_pure
 static const char *
-skip(const char * p)
+skip(const char *p) noexcept
 {
-       gint stack = 0;
+       unsigned stack = 0;
 
        while (*p != '\0') {
                if (*p == '[')
@@ -51,83 +53,69 @@ skip(const char * p)
        return p;
 }
 
-#ifndef NCMPC_MINI
-
 static char *
-concat_tag_values(const char *a, const char *b)
+CopyString(char *dest, char *const dest_end,
+          const char *src, size_t length) noexcept
 {
-       return g_strconcat(a, ", ", b, nullptr);
+       if (length >= size_t(dest_end - dest))
+               length = dest_end - dest - 1;
+
+       return std::copy_n(src, length, dest);
 }
 
 static char *
-song_more_tag_values(const struct mpd_song *song, enum mpd_tag_type tag,
-                    const char *first)
+CopyStringFromUTF8(char *dest, char *const dest_end,
+                  const char *src_utf8) noexcept
 {
-       const char *p = mpd_song_get_tag(song, tag, 1);
-       if (p == nullptr)
-               return nullptr;
-
-       char *buffer = concat_tag_values(first, p);
-       for (unsigned i = 2; (p = mpd_song_get_tag(song, tag, i)) != nullptr;
-            ++i) {
-               char *prev = buffer;
-               buffer = concat_tag_values(buffer, p);
-               g_free(prev);
-       }
-
-       return buffer;
+       return CopyUtf8ToLocale(dest, dest_end - dest, src_utf8);
 }
 
-#endif /* !NCMPC_MINI */
-
 static char *
-song_tag_locale(const struct mpd_song *song, enum mpd_tag_type tag)
+CopyTag(char *dest, char *const end,
+       const struct mpd_song *song, enum mpd_tag_type tag) noexcept
 {
        const char *value = mpd_song_get_tag(song, tag, 0);
        if (value == nullptr)
-               return nullptr;
-
-#ifndef NCMPC_MINI
-       char *all = song_more_tag_values(song, tag, value);
-       if (all != nullptr)
-               value = all;
-#endif /* !NCMPC_MINI */
+               return dest;
 
-       char *result = utf8_to_locale(value);
+       dest = CopyStringFromUTF8(dest, end, value);
 
-#ifndef NCMPC_MINI
-       g_free(all);
-#endif /* !NCMPC_MINI */
+       for (unsigned i = 1; dest + 5 < end &&
+                    (value = mpd_song_get_tag(song, tag, i)) != nullptr;
+            ++i) {
+               *dest++ = ',';
+               *dest++ = ' ';
+               dest = CopyStringFromUTF8(dest, end, value);
+       }
 
-       return result;
+       return dest;
 }
 
 static size_t
-_strfsong(char *s,
-         size_t max,
+_strfsong(char *const s0, char *const end,
          const char *format,
          const struct mpd_song *song,
-         const char **last)
+         const char **last) noexcept
 {
        bool found = false;
        /* "missed" helps handling the case of mere literal text like
           found==true instead of found==false. */
        bool missed = false;
 
-       s[0] = '\0';
 
-       if (song == nullptr)
+       if (song == nullptr) {
+               s0[0] = '\0';
                return 0;
+       }
 
+       char *s = s0;
        const char *p;
-       size_t length = 0;
-       for (p = format; *p != '\0' && length<max;) {
+       for (p = format; *p != '\0' && s < end - 1;) {
                /* OR */
                if (p[0] == '|') {
                        ++p;
                        if(missed && !found) {
-                               s[0] = '\0';
-                               length = 0;
+                               s = s0;
                                missed = false;
                        } else {
                                p = skip(p);
@@ -149,150 +137,145 @@ _strfsong(char *s,
 
                /* EXPRESSION START */
                if (p[0] == '[') {
-                       char *temp = (char *)g_malloc0(max);
-                       if( _strfsong(temp, max, p+1, song, &p) >0 ) {
-                               g_strlcat(s, temp, max);
-                               length = strlen(s);
+                       size_t n = _strfsong(s, end, p + 1,
+                                            song, &p);
+                       if (n > 0) {
+                               s += n;
                                found = true;
                        } else {
                                missed = true;
                        }
-                       g_free(temp);
                        continue;
                }
 
                /* EXPRESSION END */
                if (p[0] == ']') {
-                       if(last) *last = p+1;
-                       if(missed && !found && length) {
-                               s[0] = '\0';
-                               length = 0;
-                       }
-                       return length;
+                       ++p;
+                       if (missed && !found)
+                               s = s0;
+                       break;
                }
 
-               /* pass-through non-escaped portions of the format string */
-               if (p[0] != '#' && p[0] != '%' && length<max) {
-                       s[length++] = *p;
-                       s[length] = '\0';
-                       p++;
+               /* let the escape character escape itself */
+               if (p[0] == '#' && p[1] != '\0') {
+                       *s++ = p[1];
+                       p+=2;
                        continue;
                }
 
-               /* let the escape character escape itself */
-               if (p[0] == '#' && p[1] != '\0' && length<max) {
-                       s[length++] = *(p+1);
-                       s[length] = '\0';
-                       p+=2;
+               /* pass-through non-escaped portions of the format string */
+               if (p[0] != '%') {
+                       *s++ = *p++;
                        continue;
                }
 
                /* advance past the esc character */
 
                /* find the extent of this format specifier (stop at \0, ' ', or esc) */
-               char *temp = nullptr;
-               const char *end = p + 1;
-               while(*end >= 'a' && *end <= 'z') {
-                       end++;
-               }
-               size_t n = end - p + 1;
-               if(*end != '%')
+               const char *name_end = p + 1;
+               while (*name_end >= 'a' && *name_end <= 'z')
+                       ++name_end;
+               size_t n = name_end - p + 1;
+
+               const char *value = nullptr, *value_utf8 = nullptr;
+               enum mpd_tag_type tag = MPD_TAG_UNKNOWN;
+               bool short_tag = false;
+               char buffer[32];
+
+               if (*name_end != '%')
                        n--;
                else if (strncmp("%file%", p, n) == 0)
-                       temp = utf8_to_locale(mpd_song_get_uri(song));
-               else if (strncmp("%artist%", p, n) == 0) {
-                       temp = song_tag_locale(song, MPD_TAG_ARTIST);
-                       if (temp == nullptr) {
-                               temp = song_tag_locale(song, MPD_TAG_PERFORMER);
-                               if (temp == nullptr)
-                                       temp = song_tag_locale(song, MPD_TAG_COMPOSER);
-                       }
-               } else if (strncmp("%albumartist", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_ALBUM_ARTIST);
+                       value_utf8 = mpd_song_get_uri(song);
+               else if (strncmp("%artist%", p, n) == 0)
+                       tag = MPD_TAG_ARTIST;
+               else if (strncmp("%albumartist%", p, n) == 0)
+                       tag = MPD_TAG_ALBUM_ARTIST;
                else if (strncmp("%composer%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_COMPOSER);
+                       tag = MPD_TAG_COMPOSER;
                else if (strncmp("%performer%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_PERFORMER);
-               else if (strncmp("%title%", p, n) == 0) {
-                       temp = song_tag_locale(song, MPD_TAG_TITLE);
-                       if (temp == nullptr)
-                               temp = song_tag_locale(song, MPD_TAG_NAME);
-               } else if (strncmp("%album%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_ALBUM);
+                       tag = MPD_TAG_PERFORMER;
+               else if (strncmp("%title%", p, n) == 0)
+                       tag = MPD_TAG_TITLE;
+               else if (strncmp("%album%", p, n) == 0)
+                       tag = MPD_TAG_ALBUM;
                else if (strncmp("%shortalbum%", p, n) == 0) {
-                       temp = song_tag_locale(song, MPD_TAG_ALBUM);
-                       if (temp) {
-                               char *temp2 = g_strndup(temp, 25);
-                               if (strlen(temp) > 25) {
-                                       temp2[24] = '.';
-                                       temp2[23] = '.';
-                                       temp2[22] = '.';
-                               }
-                               g_free(temp);
-                               temp = temp2;
-                       }
+                       tag = MPD_TAG_ALBUM;
+                       short_tag = true;
                }
                else if (strncmp("%track%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_TRACK);
+                       tag = MPD_TAG_TRACK;
                else if (strncmp("%disc%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_DISC);
+                       tag = MPD_TAG_DISC;
                else if (strncmp("%name%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_NAME);
+                       tag = MPD_TAG_NAME;
                else if (strncmp("%date%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_DATE);
+                       tag = MPD_TAG_DATE;
                else if (strncmp("%genre%", p, n) == 0)
-                       temp = song_tag_locale(song, MPD_TAG_GENRE);
+                       tag = MPD_TAG_GENRE;
                else if (strncmp("%shortfile%", p, n) == 0) {
                        const char *uri = mpd_song_get_uri(song);
                        if (strstr(uri, "://") == nullptr)
-                               uri = g_basename(uri);
-                       temp = utf8_to_locale(uri);
+                               uri = GetUriFilename(uri);
+                       value_utf8 = uri;
                } else if (strncmp("%time%", p, n) == 0) {
                        unsigned duration = mpd_song_get_duration(song);
 
                        if (duration > 0)  {
-                               char buffer[32];
                                format_duration_short(buffer, sizeof(buffer),
                                                      duration);
-                               temp = g_strdup(buffer);
+                               value = buffer;
                        }
                }
 
-               if( temp == nullptr) {
-                       size_t templen=n;
+               /* advance past the specifier */
+               p += n;
+
+               if (tag != MPD_TAG_UNKNOWN) {
+                       char *const old = s;
+                       s = CopyTag(s, end, song, tag);
+                       if (s != old) {
+                               found = true;
+
+                               if (short_tag && s > old + 25)
+                                       s = std::copy_n("...", 3, old + 22);
+                       } else
+                               missed = true;
+
+                       continue;
+               }
+
+               if (value_utf8 != nullptr) {
+                       found = true;
+                       s = CopyStringFromUTF8(s, end, value_utf8);
+                       continue;
+               }
+
+               size_t value_length;
+
+               if (value == nullptr) {
                        /* just pass-through any unknown specifiers (including esc) */
-                       if( length+templen > max )
-                               templen = max-length;
-                       char *ident = g_strndup(p, templen);
-                       g_strlcat(s, ident, max);
-                       length+=templen;
-                       g_free(ident);
+                       value = p;
+                       value_length = n;
 
                        missed = true;
                } else {
-                       size_t templen = strlen(temp);
+                       value_length = strlen(value);
 
                        found = true;
-                       if( length+templen > max )
-                               templen = max-length;
-                       g_strlcat(s, temp, max);
-                       length+=templen;
-                       g_free(temp);
                }
 
-               /* advance past the specifier */
-               p += n;
+               s = CopyString(s, end, value, value_length);
        }
 
        if(last) *last = p;
 
-       return length;
+       *s = '\0';
+       return s - s0;
 }
 
 size_t
 strfsong(char *s, size_t max, const char *format,
-        const struct mpd_song *song)
+        const struct mpd_song *song) noexcept
 {
-       return _strfsong(s, max, format, song, nullptr);
+       return _strfsong(s, s + max, format, song, nullptr);
 }
-