tagtree add %reload to allow hot reloading of tagnavi config

adds (static) function tagtree_unload
 frees all buflib allocations for tagtree

adds command %reload that allows a new tagnavi file to be loaded
without device restart
use it like so..
"Reload..." -> %reload

Allocations are now checked for validity (probably a good idea anyway)

Change-Id: I2b4b9b7e253f97f3e6575e0ea7a92905e004d47f
diff --git a/apps/tagtree.c b/apps/tagtree.c
index 12a7a74..e56c805 100644
--- a/apps/tagtree.c
+++ b/apps/tagtree.c
@@ -105,6 +105,7 @@
     var_format,
     menu_next,
     menu_load,
+    menu_reload,
 };
 
 /* Capacity 10 000 entries (for example 10k different artists) */
@@ -114,6 +115,7 @@
 #define MAX_TAGS 5
 #define MAX_MENU_ID_SIZE 32
 
+#define RELOAD_TAGTREE (-1024)
 static bool sort_inverse;
 
 /*
@@ -264,8 +266,12 @@
 
 static void* tagtree_alloc(size_t size)
 {
-    char* buf = core_get_data(tagtree_handle) + tagtree_buf_used;
     size = ALIGN_UP(size, sizeof(void*));
+    if (size > (tagtree_bufsize - tagtree_buf_used))
+        return NULL;
+
+    char* buf = core_get_data(tagtree_handle) + tagtree_buf_used;
+
     tagtree_buf_used += size;
     return buf;
 }
@@ -273,7 +279,8 @@
 static void* tagtree_alloc0(size_t size)
 {
     void* ret = tagtree_alloc(size);
-    memset(ret, 0, size);
+    if (ret)
+        memset(ret, 0, size);
     return ret;
 }
 
@@ -281,7 +288,8 @@
 {
     size_t len = strlen(buf) + 1;
     char* dest = tagtree_alloc(len);
-    strcpy(dest, buf);
+    if (dest)
+        strcpy(dest, buf);
     return dest;
 }
 
@@ -348,7 +356,8 @@
         {"%root_menu", var_rootmenu},
         {"%format", var_format},
         {"->", menu_next},
-        {"==>", menu_load}
+        {"==>", menu_load},
+        {"%reload", menu_reload}
     };
     char buf[128];
     unsigned int i;
@@ -467,7 +476,12 @@
         clause->str = tagtree_strdup(buf);
     }
 
-    if (TAGCACHE_IS_NUMERIC(clause->tag))
+    if (!clause->str)
+    {
+        logf("tagtree failed to allocate %s", "clause string");
+        return false;
+    }
+    else if (TAGCACHE_IS_NUMERIC(clause->tag))
     {
         clause->numeric = true;
         clause->numeric_data = atoi(clause->str);
@@ -581,11 +595,16 @@
 
     if (formats[format_count] == NULL)
         formats[format_count] = tagtree_alloc0(sizeof(struct display_format));
-
+    if (!formats[format_count])
+    {
+        logf("tagtree failed to allocate %s", "format string");
+        return -2;
+    }
     if (get_format_str(formats[format_count]) < 0)
     {
         logf("get_format_str() parser failed!");
-        memset(formats[format_count], 0, sizeof(struct display_format));
+        if (formats[format_count])
+            memset(formats[format_count], 0, sizeof(struct display_format));
         return -4;
     }
 
@@ -600,7 +619,7 @@
         tagtree_lock();
         while (1)
         {
-            struct tagcache_search_clause *newclause;
+            struct tagcache_search_clause *new_clause;
 
             if (clause_count >= TAGCACHE_MAX_CLAUSES)
             {
@@ -608,10 +627,14 @@
                 break;
             }
 
-            newclause = tagtree_alloc(sizeof(struct tagcache_search_clause));
-
-            formats[format_count]->clause[clause_count] = newclause;
-            if (!read_clause(newclause))
+            new_clause = tagtree_alloc(sizeof(struct tagcache_search_clause));
+            if (!new_clause)
+            {
+                logf("tagtree failed to allocate %s", "search clause");
+                return -3;
+            }
+            formats[format_count]->clause[clause_count] = new_clause;
+            if (!read_clause(new_clause))
                 break;
 
             clause_count++;
@@ -670,10 +693,16 @@
     if (clause_count >= TAGCACHE_MAX_CLAUSES)
     {
         logf("Too many clauses");
-        return false;
+        return -2;
     }
 
     new_clause = tagtree_alloc(sizeof(struct tagcache_search_clause));
+    if (!new_clause)
+    {
+        logf("tagtree failed to allocate %s", "search clause");
+        return -3;
+    }
+
     inst->clause[inst->tagorder_count][clause_count] = new_clause;
 
     if (*strp == '|')
@@ -746,6 +775,11 @@
 
         /* Allocate a new menu unless link is found. */
         menus[menu_count] = tagtree_alloc0(sizeof(struct menu_root));
+        if (!menus[menu_count])
+        {
+            logf("tagtree failed to allocate %s", "menu");
+            return false;
+        }
         strlcpy(menus[menu_count]->id, buf, MAX_MENU_ID_SIZE);
         entry->link = menu_count;
         ++menu_count;
@@ -1088,6 +1122,11 @@
                 if (menu == NULL)
                 {
                     menus[menu_count] = tagtree_alloc0(sizeof(struct menu_root));
+                    if (!menus[menu_count])
+                    {
+                        logf("tagtree failed to allocate %s", "menu");
+                        return -2;
+                    }
                     menu = menus[menu_count];
                     ++menu_count;
                     strlcpy(menu->id, data, MAX_MENU_ID_SIZE);
@@ -1135,7 +1174,11 @@
     /* Allocate */
     if (menu->items[menu->itemcount] == NULL)
         menu->items[menu->itemcount] = tagtree_alloc0(sizeof(struct menu_entry));
-
+    if (!menu->items[menu->itemcount])
+    {
+        logf("tagtree failed to allocate %s", "menu items");
+        return -2;
+    }
     tagtree_lock();
     if (parse_search(menu->items[menu->itemcount], buf))
         menu->itemcount++;
@@ -1148,6 +1191,7 @@
 {
     int fd;
     char buf[1024];
+    int rc;
 
     if (menu_count >= TAGMENU_MAX_MENUS)
     {
@@ -1163,10 +1207,58 @@
     }
 
     /* Now read file for real, parsing into si */
-    fast_readline(fd, buf, sizeof buf, NULL, parse_line);
+    rc = fast_readline(fd, buf, sizeof buf, NULL, parse_line);
     close(fd);
 
-    return true;
+    return (rc >= 0);
+}
+
+static void tagtree_unload(struct tree_context *c)
+{
+    int i;
+    tagtree_lock();
+
+    remove_event(PLAYBACK_EVENT_TRACK_BUFFER, tagtree_buffer_event);
+    remove_event(PLAYBACK_EVENT_TRACK_FINISH, tagtree_track_finish_event);
+
+    if (c)
+    {
+        tree_lock_cache(c);
+        struct tagentry *dptr = core_get_data(c->cache.entries_handle);
+        menu = menus[c->currextra];
+        if (!menu)
+        {
+            logf("tagtree menu doesn't exist");
+            return;
+        }
+
+        for (i = 0; i < menu->itemcount; i++)
+        {
+            dptr->name = NULL;
+            dptr->newtable = 0;
+            dptr->extraseek = 0;
+            dptr++;
+        }
+    }
+
+    for (i = 0; i < menu_count; i++)
+        menus[i] = NULL;
+    menu_count = 0;
+
+    for (i = 0; i < format_count; i++)
+        formats[i] = NULL;
+    format_count = 0;
+
+    core_free(tagtree_handle);
+    tagtree_handle   = 0;
+    tagtree_buf_used = 0;
+    tagtree_bufsize  = 0;
+
+    if (c)
+        tree_unlock_cache(c);
+    tagtree_unlock();
+    if (lock_count > 0)
+        tagtree_unlock();/* second unlock to enable re-init */
 }
 
 void tagtree_init(void)
@@ -1176,7 +1268,11 @@
     menu = NULL;
     rootmenu = -1;
     tagtree_handle = core_alloc_maximum("tagtree", &tagtree_bufsize, &ops);
-    parse_menu(FILE_SEARCH_INSTRUCTIONS);
+    if (!parse_menu(FILE_SEARCH_INSTRUCTIONS))
+    {
+        tagtree_unload(NULL);
+        return;
+    }
 
     /* safety check since tree.c needs to cast tagentry to entry */
     if (sizeof(struct tagentry) != sizeof(struct entry))
@@ -1358,6 +1454,9 @@
     else
         tag = csi->tagorder[level];
 
+    if (tag == menu_reload)
+        return RELOAD_TAGTREE;
+
     if (!tagcache_search(&tcs, tag))
         return -1;
 
@@ -1691,9 +1790,16 @@
 
     if (count < 0)
     {
+        if (count != RELOAD_TAGTREE)
+            splash(HZ, str(LANG_TAGCACHE_BUSY));
+        else /* unload and re-init tagtree */
+        {
+            splash(HZ, str(LANG_WAIT));
+            tagtree_unload(c);
+            tagtree_init();
+        }
         c->dirlevel = 0;
         count = load_root(c);
-        splash(HZ, str(LANG_TAGCACHE_BUSY));
     }
 
     /* The _total_ numer of entries available. */