Update PluginManager: implement plugin tags
authorRobin Gareus <robin@gareus.org>
Mon, 29 Jan 2018 23:33:44 +0000 (00:33 +0100)
committerRobin Gareus <robin@gareus.org>
Tue, 30 Jan 2018 00:34:14 +0000 (01:34 +0100)
* move plugin-meta-data (status, tag) into dedicated sub-dir
* load/save space separated tags
* pre-seed tags with plugin-category (if unset)
* breaking API change: PluginStatusesChanged() signal includes change

libs/ardour/ardour/plugin_manager.h
libs/ardour/ardour/types_convert.h
libs/ardour/plugin_manager.cc

index 70b49d3eac72a73bf89cba2f7f7994efcdc79663..b10c1a1f21e8cc66a830dff31e42e9d202a85b39 100644 (file)
@@ -39,7 +39,7 @@ namespace ARDOUR {
 class Plugin;
 
 class LIBARDOUR_API PluginManager : public boost::noncopyable {
-  public:
+public:
        static PluginManager& instance();
        static std::string scanner_bin_path;
 
@@ -64,6 +64,9 @@ class LIBARDOUR_API PluginManager : public boost::noncopyable {
        const std::string get_default_windows_vst_path() const { return windows_vst_path; }
        const std::string get_default_lxvst_path() const { return lxvst_path; }
 
+       /* always return LXVST for any VST subtype */
+       static PluginType to_generic_vst (PluginType);
+
        bool cancelled () { return _cancel_scan; }
        bool no_timeout () { return _cancel_timeout; }
 
@@ -79,12 +82,52 @@ class LIBARDOUR_API PluginManager : public boost::noncopyable {
        void set_status (ARDOUR::PluginType type, std::string unique_id, PluginStatusType status);
        PluginStatusType get_status (const PluginInfoPtr&) const;
 
+       void load_tags ();
+       void save_tags ();
+
+       void set_tags (ARDOUR::PluginType type, std::string unique_id, std::string tags, bool factory, bool force = false);
+       std::string get_tags_as_string (PluginInfoPtr const&) const;
+       std::vector<std::string> get_tags (PluginInfoPtr const&) const;
+       std::vector<std::string> get_all_tags (bool favorites_only) const;
+
        /** plugins were added to or removed from one of the PluginInfoLists */
        PBD::Signal0<void> PluginListChanged;
+
        /** Plugin Hidden/Favorite status changed */
-       PBD::Signal0<void> PluginStatusesChanged;
+       PBD::Signal3<void, ARDOUR::PluginType, std::string, PluginStatusType> PluginStatusesChanged; //PluginType t, string id, string tag
+
+       PBD::Signal3<void, ARDOUR::PluginType, std::string, std::string> PluginTagsChanged; //PluginType t, string id, string tag
+
+private:
+
+       struct PluginTag {
+           ARDOUR::PluginType type;
+           std::string unique_id;
+           std::string tags;
+                       bool user_set;
+
+           PluginTag (ARDOUR::PluginType t, std::string id, std::string s, bool user_set)
+           : type (t), unique_id (id), tags (s), user_set (user_set) {}
+
+           bool operator== (PluginTag const& other) const {
+                   return other.type == type && other.unique_id == unique_id;
+           }
+
+           bool operator< (PluginTag const& other) const {
+                   if (other.type < type) {
+                           return true;
+                   } else if (other.type == type && other.unique_id < unique_id) {
+                           return true;
+                   }
+                   return false;
+           }
+       };
+
+       typedef std::set<PluginTag> PluginTagList;
+       PluginTagList ptags;
+
+       std::string sanitize_tag (const std::string) const;
 
-  private:
        struct PluginStatus {
            ARDOUR::PluginType type;
            std::string unique_id;
index 25381516b6a77d9eeeae20b27b2c2972a2e5022c..83176149893288ea212500b5fa8c181c6d43d9e4 100644 (file)
@@ -63,6 +63,7 @@ DEFINE_ENUM_CONVERT(ARDOUR::DiskIOPoint)
 DEFINE_ENUM_CONVERT(ARDOUR::NoteMode)
 DEFINE_ENUM_CONVERT(ARDOUR::ChannelMode)
 DEFINE_ENUM_CONVERT(ARDOUR::MonitorChoice)
+DEFINE_ENUM_CONVERT(ARDOUR::PluginType)
 
 DEFINE_ENUM_CONVERT(ARDOUR::AlignStyle)
 DEFINE_ENUM_CONVERT(ARDOUR::AlignChoice)
index 766daa306b987f4ba5cc5ec36e52292bf2335963..6df915279ce930e8d8893af7bce32a8ee84d022b 100644 (file)
@@ -71,6 +71,7 @@
 
 #include "pbd/whitespace.h"
 #include "pbd/file_utils.h"
+#include "pbd/tokenizer.h"
 
 #include "ardour/directory_names.h"
 #include "ardour/debug.h"
@@ -172,6 +173,8 @@ PluginManager::PluginManager ()
 
        load_statuses ();
 
+       load_tags ();
+
        if ((s = getenv ("LADSPA_RDF_PATH"))){
                lrdf_path = s;
        }
@@ -505,6 +508,7 @@ PluginManager::lua_refresh ()
        for (LuaScriptList::const_iterator s = _scripts.begin(); s != _scripts.end(); ++s) {
                LuaPluginInfoPtr lpi (new LuaPluginInfo(*s));
                _lua_plugin_info->push_back (lpi);
+               set_tags (lpi->type, lpi->unique_id, lpi->category, true);
        }
 }
 
@@ -795,6 +799,10 @@ PluginManager::lv2_refresh ()
        DEBUG_TRACE (DEBUG::PluginManager, "LV2: refresh\n");
        delete _lv2_plugin_info;
        _lv2_plugin_info = LV2PluginInfo::discover();
+
+       for (PluginInfoList::iterator i = _lv2_plugin_info->begin(); i != _lv2_plugin_info->end(); ++i) {
+               set_tags ((*i)->type, (*i)->unique_id, (*i)->category, true);
+       }
 }
 #endif
 
@@ -815,6 +823,10 @@ PluginManager::au_refresh (bool cache_only)
        // successful scan re-enabled automatic discovery if it was set
        Config->set_discover_audio_units (discover_at_start);
        Config->save_state();
+
+       for (PluginInfoList::iterator i = _au_plugin_info->begin(); i != _au_plugin_info->end(); ++i) {
+               set_tags ((*i)->type, (*i)->unique_id, (*i)->category, true);
+       }
 }
 
 #endif
@@ -994,6 +1006,9 @@ PluginManager::windows_vst_discover (string path, bool cache_only)
                info->n_outputs.set_midi ((finfo->wantMidi&2) ? 1 : 0);
                info->type = ARDOUR::Windows_VST;
 
+               /* if we don't have any tags for this plugin, make some up. */
+               set_tags (info->type, info->unique_id, info->category, true);
+
                // TODO: check dup-IDs (lxvst AND windows vst)
                bool duplicate = false;
 
@@ -1131,6 +1146,9 @@ PluginManager::mac_vst_discover (string path, bool cache_only)
                info->n_outputs.set_midi ((finfo->wantMidi&2) ? 1 : 0);
                info->type = ARDOUR::MacVST;
 
+               /* if we don't have any tags for this plugin, make some up. */
+               set_tags (info->type, info->unique_id, info->category, true);
+
                bool duplicate = false;
                if (!_mac_vst_plugin_info->empty()) {
                        for (PluginInfoList::iterator i =_mac_vst_plugin_info->begin(); i != _mac_vst_plugin_info->end(); ++i) {
@@ -1287,7 +1305,7 @@ PluginManager::get_status (const PluginInfoPtr& pi) const
 {
        PluginStatus ps (pi->type, pi->unique_id);
        PluginStatusList::const_iterator i =  find (statuses.begin(), statuses.end(), ps);
-       if (i ==  statuses.end() ) {
+       if (i ==  statuses.end()) {
                return Normal;
        } else {
                return i->status;
@@ -1297,7 +1315,7 @@ PluginManager::get_status (const PluginInfoPtr& pi) const
 void
 PluginManager::save_statuses ()
 {
-       std::string path = Glib::build_filename (user_config_directory(), "plugin_statuses");
+       std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_statuses");
        stringstream ofs;
 
        for (PluginStatusList::iterator i = statuses.begin(); i != statuses.end(); ++i) {
@@ -1340,17 +1358,18 @@ PluginManager::save_statuses ()
                }
 
                ofs << ' ';
+
                ofs << (*i).unique_id;;
                ofs << endl;
        }
        g_file_set_contents (path.c_str(), ofs.str().c_str(), -1, NULL);
-       PluginStatusesChanged (); /* EMIT SIGNAL */
 }
 
 void
 PluginManager::load_statuses ()
 {
-       std::string path = Glib::build_filename (user_config_directory(), "plugin_statuses");
+       std::string path;
+       find_file (plugin_metadata_search_path(), "plugin_statuses", path);  //note: if no user folder is found, this will find the resources path
        gchar *fbuf = NULL;
        if (!g_file_get_contents (path.c_str(), &fbuf, NULL, NULL))  {
                return;
@@ -1431,11 +1450,65 @@ PluginManager::set_status (PluginType t, string id, PluginStatusType status)
        PluginStatus ps (t, id, status);
        statuses.erase (ps);
 
-       if (status == Normal) {
-               return;
+       if (status != Normal) {
+               statuses.insert (ps);
        }
 
-       statuses.insert (ps);
+       PluginStatusesChanged (t, id, status); /* EMIT SIGNAL */
+}
+
+PluginType
+PluginManager::to_generic_vst (const PluginType t)
+{
+       switch (t) {
+               case Windows_VST:
+               case LXVST:
+               case MacVST:
+                       return LXVST;
+               default:
+                       break;
+       }
+       return t;
+}
+
+struct SortByTag {
+       bool operator() (std::string a, std::string b) {
+               return a.compare (b) < 0;
+       }
+};
+
+vector<std::string>
+PluginManager::get_tags (const PluginInfoPtr& pi) const
+{
+       vector<std::string> tags;
+
+       PluginTag ps (to_generic_vst(pi->type), pi->unique_id, "", false);
+       PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
+       if (i != ptags.end ()) {
+
+               if (!PBD::tokenize (i->tags, string(" "), std::back_inserter (tags), true)) {
+                       cout << _("PluginManager: Could not tokenize string: ") << i->tags << endmsg;
+               }
+               SortByTag sorter;
+               sort (tags.begin(), tags.end(), sorter);
+       }
+       return tags;
+}
+
+std::string
+PluginManager::get_tags_as_string (const PluginInfoPtr& pi) const
+{
+       std::string ret;
+
+       vector<std::string> tags = get_tags(pi);
+       for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
+               if (t != tags.begin ()) {
+                       ret.append(" ");
+               }
+               ret.append(*t);
+       }
+
+       return ret;
 }
 
 std::string
@@ -1446,6 +1519,159 @@ PluginManager::user_plugin_metadata_dir () const
        return dir;
 }
 
+void
+PluginManager::save_tags ()
+{
+       std::string path = Glib::build_filename (user_plugin_metadata_dir(), "plugin_tags");
+       XMLNode* root = new XMLNode (X_("PluginTags"));
+
+       for (PluginTagList::iterator i = ptags.begin(); i != ptags.end(); ++i) {
+               if (!(*i).user_set) {
+                       continue;
+               }
+               XMLNode* node = new XMLNode (X_("Plugin"));
+               node->set_property (X_("type"), to_generic_vst ((*i).type));
+               node->set_property (X_("id"), (*i).unique_id);
+               node->set_property (X_("tags"), (*i).tags);
+               node->set_property (X_("user-set"), (*i).user_set);
+               root->add_child_nocopy (*node);
+       }
+
+       XMLTree tree;
+       tree.set_root (root);
+       if (!tree.write (path)) {
+               error << string_compose (_("Could not save Plugin Tags info to %1"), path) << endmsg;
+       }
+}
+
+void
+PluginManager::load_tags ()
+{
+       vector<std::string> tmp;
+       find_files_matching_pattern (tmp, plugin_metadata_search_path (), "plugin_tags");
+
+       for (vector<std::string>::const_iterator p = tmp.begin (); p != tmp.end(); ++p) {
+               std::string path = *p;
+               info << string_compose (_("Loading plugin meta data file %1"), path) << endmsg;
+               if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
+                       return;
+               }
+
+               XMLTree tree;
+               if (!tree.read (path)) {
+                       error << string_compose (_("Cannot parse plugin tag info from %1"), path) << endmsg;
+                       return;
+               }
+
+               for (XMLNodeConstIterator i = tree.root()->children().begin(); i != tree.root()->children().end(); ++i) {
+                       PluginType type;
+                       string id;
+                       string tags;
+                       bool user_set;
+                       if (!(*i)->get_property (X_("type"), type) ||
+                                       !(*i)->get_property (X_("id"), id) ||
+                                       !(*i)->get_property (X_("tags"), tags)) {
+                       }
+                       if (!(*i)->get_property (X_("user-set"), user_set)) {
+                               user_set = false;
+                       }
+                       strip_whitespace_edges (tags);
+                       set_tags (type, id, tags, !user_set);
+               }
+       }
+}
+
+void
+PluginManager::set_tags (PluginType t, string id, string tag, bool factory, bool force)
+{
+       string sanitized = sanitize_tag (tag);
+
+       PluginTag ps (to_generic_vst (t), id, sanitized, !factory);
+       PluginTagList::const_iterator i = find (ptags.begin(), ptags.end(), ps);
+       if (i == ptags.end()) {
+               ptags.insert (ps);
+       } else {
+               if (!(*i).user_set || force || ((*i).user_set && !factory)) {
+                       ptags.erase (ps);
+                       ptags.insert (ps);
+               }
+       }
+       if (!factory || force) {
+               PluginTagsChanged (t, id, sanitized); /* EMIT SIGNAL */
+       }
+}
+
+std::string
+PluginManager::sanitize_tag (const std::string to_sanitize) const
+{
+       string sanitized = to_sanitize;
+       vector<string> tags;
+       if (!PBD::tokenize (sanitized, string(" "), std::back_inserter (tags), true)) {
+               cout << _("PluginManager::sanitize_tag could not tokenize string: ") << sanitized << endmsg;
+               return "";
+       }
+
+       /* convert tokens to lower-case, comma-separated list */
+       sanitized = "";
+       for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
+               string temp(*t);
+               std::transform (temp.begin(), temp.end(), temp.begin(), ::tolower);
+               sanitized.append(temp);
+               sanitized.append(" ");
+       }
+
+       /* remove trailing space */
+       if (sanitized.length() > 0) {
+               sanitized.erase (sanitized.length()-1, 1);
+       }
+
+       return sanitized;
+}
+
+std::vector<std::string>
+PluginManager::get_all_tags (bool favorites_only) const
+{
+       std::vector<std::string> ret;
+
+       PluginTagList::const_iterator pt;
+       for (pt = ptags.begin(); pt != ptags.end(); ++pt) {
+
+               /* if favorites_only then we need to check the info ptr and maybe skip */
+               if (favorites_only) {
+                       PluginStatus stat ((*pt).type, (*pt).unique_id);
+                       PluginStatusList::const_iterator i =  find (statuses.begin(), statuses.end(), stat);
+                       if ((i != statuses.end()) && (i->status == Favorite)) {
+                               /* it's a favorite! */
+                       } else {
+                               continue;
+                       }
+               }
+
+               /* parse each plugin's tag string into separate tags */
+               vector<string> tags;
+               if (!PBD::tokenize ((*pt).tags, string(",\n"), std::back_inserter (tags), true)) {
+                       cout << _("PluginManager: Could not tokenize string: ") << (*pt).tags << endmsg;
+                       continue;
+               }
+
+               /* maybe add the tags we've found */
+               for (vector<string>::iterator t = tags.begin(); t != tags.end(); ++t) {
+                       /* if this tag isn't already in the list, add it */
+                       vector<string>::iterator i =  find (ret.begin(), ret.end(), *t);
+                       if (i == ret.end()) {
+                               ret.push_back (*t);
+                       }
+               }
+       }
+
+       /* sort in alphabetical order */
+       SortByTag sorter;
+       sort (ret.begin(), ret.end(), sorter);
+
+       return ret;
+}
+
+
 const ARDOUR::PluginInfoList&
 PluginManager::windows_vst_plugin_info ()
 {