[smuxi/experiments/configurable_message_patterns: 1/6] Engine: implemented user configurable message patterns



commit 25f55c051426c88ae9c999a544a4755fa27c5106
Author: Mirco Bauer <meebey meebey net>
Date:   Sat Feb 15 14:32:01 2014 +0100

    Engine: implemented user configurable message patterns

 src/Engine-IRC/Protocols/Irc/IrcMessageBuilder.cs |    2 +-
 src/Engine/Config/Config.cs                       |   16 ++
 src/Engine/Config/MessageBuilderSettings.cs       |  109 +++++++-------
 src/Engine/Config/MessagePatternModel.cs          |  134 +++++++++++++++++
 src/Engine/Config/MessagePatternSettings.cs       |  160 +++++++++++++++++++++
 src/Engine/Engine.csproj                          |    2 +
 src/Engine/Makefile.am                            |    2 +
 src/Engine/Messages/MessageBuilder.cs             |   92 +++++++------
 src/Engine/Session.cs                             |   56 +++++---
 9 files changed, 458 insertions(+), 115 deletions(-)
---
diff --git a/src/Engine-IRC/Protocols/Irc/IrcMessageBuilder.cs 
b/src/Engine-IRC/Protocols/Irc/IrcMessageBuilder.cs
index 94deaa7..f4f7caf 100644
--- a/src/Engine-IRC/Protocols/Irc/IrcMessageBuilder.cs
+++ b/src/Engine-IRC/Protocols/Irc/IrcMessageBuilder.cs
@@ -209,7 +209,7 @@ namespace Smuxi.Engine
                 msgPart.Italic = italic;
                 msgPart.ForegroundColor = fg_color;
                 msgPart.BackgroundColor = bg_color;
-                Append(ParseSmartLinks(msgPart));
+                Append(ParsePatterns(msgPart));
             } while (controlCharFound);
             return this;
         }
diff --git a/src/Engine/Config/Config.cs b/src/Engine/Config/Config.cs
index 4ab2a86..051c806 100644
--- a/src/Engine/Config/Config.cs
+++ b/src/Engine/Config/Config.cs
@@ -618,6 +618,22 @@ namespace Smuxi.Engine
                     LoadUserEntry(user, cprefix + "MessageType", null);
                     LoadUserEntry(user, cprefix + "MessagePattern", null);
                 }
+
+                string lprefix = "MessagePatterns/";
+                var linkKeys = GetList(prefix + user + "/" + lprefix + "MessagePatterns");
+                if (linkKeys == null) {
+                    linkKeys = new string[] {};
+                    m_Preferences[prefix + user + "/" + lprefix + "MessagePatterns"] = new string[] {};
+                } else {
+                    m_Preferences[prefix + user + "/" + lprefix + "MessagePatterns"] = linkKeys;
+                }
+                foreach (var linkKey in linkKeys) {
+                    lprefix = "MessagePatterns/" + linkKey + "/";
+                    LoadUserEntry(user, lprefix + "MessagePartPattern", String.Empty);
+                    LoadUserEntry(user, lprefix + "MessagePartType", String.Empty);
+                    LoadUserEntry(user, lprefix + "LinkFormat", String.Empty);
+                    LoadUserEntry(user, lprefix + "TextFormat", String.Empty);
+                }
             }
         }
 
diff --git a/src/Engine/Config/MessageBuilderSettings.cs b/src/Engine/Config/MessageBuilderSettings.cs
index cf1da9c..0a41d13 100644
--- a/src/Engine/Config/MessageBuilderSettings.cs
+++ b/src/Engine/Config/MessageBuilderSettings.cs
@@ -20,46 +20,39 @@
 using System;
 using System.Text.RegularExpressions;
 using System.Collections.Generic;
+using Smuxi.Common;
 
 namespace Smuxi.Engine
 {
     public class MessageBuilderSettings
     {
-        public class SmartLink
-        {
-            public Regex MessagePartPattern { get; set; }
-            public Type MessagePartType { get; set; }
-            // what is linked to
-            public string LinkFormat { get; set; }
-            // what is displayed
-            public string TextFormat { get; set; }
-
-            public SmartLink(Regex pattern)
-            {
-                if (pattern == null) {
-                    throw new ArgumentNullException("pattern");
-                }
-                MessagePartPattern = pattern;
-                MessagePartType = typeof(UrlMessagePartModel);
-            }
-        }
-
-        static List<SmartLink> BuiltinSmartLinks { get; set; }
-        public List<SmartLink> SmartLinks { get; private set; }
+        static List<MessagePatternModel> BuiltinPatterns { get; set; }
+        static List<MessagePatternModel> GlobalPatterns { get; set; }
+        public List<MessagePatternModel> Patterns { get; private set; }
 
         static MessageBuilderSettings()
         {
-            BuiltinSmartLinks = new List<SmartLink>();
+            BuiltinPatterns = new List<MessagePatternModel>();
             InitBuiltinSmartLinks();
+            GlobalPatterns = new List<MessagePatternModel>();
         }
 
         public MessageBuilderSettings()
         {
-            // No need to lock BuiltinSmartLinks as List<T> is thread-safe for
+            var builtinPatterns = BuiltinPatterns;
+            var globalPatterns = GlobalPatterns;
+
+            // No need to lock BuiltinPatterns as List<T> is thread-safe for
             // multiple readers as long as there is no writer at the same time.
             // BuiltinSmartLinks is only written once before the first instance
             // of MessageBuilderSettings is created via the static initializer.
-            SmartLinks = new List<SmartLink>(BuiltinSmartLinks);
+            Patterns = new List<MessagePatternModel>(builtinPatterns.Count +
+                                                     globalPatterns.Count);
+            Patterns.AddRange(builtinPatterns);
+
+            // GlobalPatterns is only set atomically in ApplyStaticConfig()
+            // and the collection is never modified after that
+            Patterns.AddRange(globalPatterns);
         }
 
         static void InitBuiltinSmartLinks()
@@ -86,7 +79,7 @@ namespace Smuxi.Engine
                 @"(<[1-9][0-9]* attachments?>) 
(http://www\.facebook\.com/messages/\?action=read&tid=[0-9a-f]+)",
                 RegexOptions.Compiled
             );
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "{2}",
                 TextFormat = "{1}",
             });
@@ -96,14 +89,14 @@ namespace Smuxi.Engine
                 protocol_user_domain_port_path,
                 RegexOptions.IgnoreCase | RegexOptions.Compiled
             );
-            BuiltinSmartLinks.Add(new SmartLink(regex));
+            BuiltinPatterns.Add(new MessagePatternModel(regex));
 
             // email
             regex = new Regex(
                 @"(?:mailto:)?(" + user_domain + ")",
                 RegexOptions.IgnoreCase | RegexOptions.Compiled
             );
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "mailto:{1}";
             });
 
@@ -113,31 +106,31 @@ namespace Smuxi.Engine
             string heuristic_domain = @"(?:(?:" + subdomain + ")+(?:" + common_tld + ")|localhost)";
             string heuristic_address = heuristic_domain + "(?:" + path + ")?";
             regex = new Regex(heuristic_address, RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://{0}";
             });
 
             // Smuxi bugtracker
             regex = new Regex(@"smuxi#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://www.smuxi.org/issues/show/{1}";
             });
 
             // RFCs
             regex = new Regex(@"RFC[ -]?([0-9]+) (?:s\.|ss\.|sec\.|sect\.|section) ?([1-9][0-9.]*)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://tools.ietf.org/html/rfc{1}#section-{2}";
             });
             regex = new Regex(@"RFC[ -]?([0-9]+) (?:p\.|pp\.|page) ?(" + short_number + ")",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://tools.ietf.org/html/rfc{1}#page-{2}";
             });
             regex = new Regex(@"RFC[ -]?([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://www.ietf.org/rfc/rfc{1}.txt";
             });
 
@@ -147,147 +140,147 @@ namespace Smuxi.Engine
             // boost bugtracker
             regex = new Regex(@"boost#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "https://svn.boost.org/trac/boost/ticket/{1}";
             });
 
             // Claws bugtracker
             regex = new Regex(@"claws#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://www.thewildbeast.co.uk/claws-mail/bugzilla/show_bug.cgi?id={1}";
             });
 
             // CVE list
             regex = new Regex(@"CVE-[0-9]{4}-[0-9]{4,}",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://cve.mitre.org/cgi-bin/cvename.cgi?name={0}";
             });
 
             // CPAN bugtracker
             regex = new Regex(@"RT#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://rt.cpan.org/Public/Bug/Display.html?id={1}";
             });
 
             // Debian bugtracker
             regex = new Regex(@"deb#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugs.debian.org/{1}";
             });
 
             // Debian Security Advisories (DSA)
             regex = new Regex(@"DSA-([0-9]{4})(-[0-9]{1,2})?",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://www.debian.org/security/dsa-{1}";
             });
 
             // openSUSE feature tracker
             regex = new Regex(@"fate#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://features.opensuse.org/{1}";
             });
 
             // freedesktop bugtracker
             regex = new Regex(@"fdo#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugs.freedesktop.org/{1}";
             });
 
             // GNU bugtracker
             regex = new Regex(@"gnu#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://debbugs.gnu.org/{1}";
             });
 
             // GCC bugtracker
             regex = new Regex(@"gcc#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://gcc.gnu.org/bugzilla/show_bug.cgi?id={1}";
             });
 
             // GNOME bugtracker
             regex = new Regex(@"bgo#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.gnome.org/{1}";
             });
 
             // KDE bugtracker
             regex = new Regex(@"kde#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugs.kde.org/{1}";
             });
 
             // kernel bugtracker
             regex = new Regex(@"bko#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.kernel.org/show_bug.cgi?id={1}";
             });
 
             // launchpad bugtracker
             regex = new Regex(@"LP#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://launchpad.net/bugs/{1}";
             });
 
             // Mozilla bugtracker
             regex = new Regex(@"bmo#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.mozilla.org/{1}";
             });
 
             // Novell bugtracker
             regex = new Regex(@"bnc#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.novell.com/{1}";
             });
 
             // Redhat bugtracker
             regex = new Regex(@"rh#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.redhat.com/{1}";
             });
 
             // Samba bugtracker
             regex = new Regex(@"bso#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.samba.org/show_bug.cgi?id={1}";
             });
 
             // sourceforge bugtracker
             regex = new Regex(@"sf#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://sf.net/support/tracker.php?aid={1}";
             });
 
             // Xfce bugtracker
             regex = new Regex(@"bxo#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.xfce.org/show_bug.cgi?id={1}";
             });
 
             // Xamarin bugtracker
             regex = new Regex(@"bxc#([0-9]+)",
                               RegexOptions.IgnoreCase | RegexOptions.Compiled);
-            BuiltinSmartLinks.Add(new SmartLink(regex) {
+            BuiltinPatterns.Add(new MessagePatternModel(regex) {
                 LinkFormat = "http://bugzilla.xamarin.com/show_bug.cgi?id={1}";
             });
 
@@ -304,8 +297,16 @@ namespace Smuxi.Engine
             // TODO: JID
         }
 
-        public void ApplyConfig(UserConfig userConfig)
+        public static void ApplyStaticConfig(UserConfig userConfig)
         {
+            Trace.Call(userConfig);
+
+            if (userConfig == null) {
+                throw new ArgumentNullException("userConfig");
+            }
+
+            var settings = new MessagePatternSettings(userConfig);
+            GlobalPatterns = settings.GetList();
         }
     }
 }
diff --git a/src/Engine/Config/MessagePatternModel.cs b/src/Engine/Config/MessagePatternModel.cs
new file mode 100644
index 0000000..b32ffe5
--- /dev/null
+++ b/src/Engine/Config/MessagePatternModel.cs
@@ -0,0 +1,134 @@
+// Smuxi - Smart MUltipleXed Irc
+//
+// Copyright (c) 2014 Mirco Bauer <meebey meebey net>
+//
+// Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+using System;
+using System.Text.RegularExpressions;
+
+namespace Smuxi.Engine
+{
+    public class MessagePatternModel
+    {
+        public int? ID { get; set; }
+        public Regex MessagePartPattern { get; set; }
+        public Type MessagePartType { get; set; }
+        // what is linked to
+        public string LinkFormat { get; set; }
+        // what is displayed
+        public string TextFormat { get; set; }
+
+        protected string ConfigKeyPrefix {
+            get {
+                if (ID == null) {
+                    throw new ArgumentNullException("ID");
+                }
+                return "MessagePatterns/" + ID + "/";
+            }
+        }
+
+        public MessagePatternModel(Regex pattern)
+        {
+            if (pattern == null) {
+                throw new ArgumentNullException("pattern");
+            }
+            MessagePartPattern = pattern;
+            MessagePartType = typeof(UrlMessagePartModel);
+        }
+
+        public MessagePatternModel(int id)
+        {
+            ID = id;
+        }
+
+        public void Load(UserConfig config)
+        {
+            if (ID == null) {
+                throw new InvalidOperationException("ID must not be null.");
+            }
+
+            Load(config, ID.Value);
+        }
+
+        public virtual void Load(UserConfig config, int id)
+        {
+            if (config == null) {
+                throw new ArgumentNullException("config");
+            }
+
+            // don't use ConfigKeyPrefix, so exception guarantees can be kept
+            string prefix = "MessagePatterns/" + id + "/";
+            if (config[prefix + "MessagePartPattern"] == null) {
+                // SmartLink does not exist
+                throw new ArgumentException("MessagePattern ID not found in config", "id");
+            }
+
+            ID = id;
+            // now we have a valid ID, ConfigKeyPrefix works
+            var messagePartPattern = (string) config[ConfigKeyPrefix + "MessagePartPattern"];
+            if (messagePartPattern.StartsWith("/") && messagePartPattern.EndsWith("/i")) {
+                var regexPattern = messagePartPattern.Substring(1, messagePartPattern.Length - 3);
+                MessagePartPattern = new Regex(regexPattern, RegexOptions.IgnoreCase | 
RegexOptions.Compiled);
+            } else {
+                MessagePartPattern = new Regex(messagePartPattern, RegexOptions.Compiled);
+            }
+            var messagePartType = (string) config[ConfigKeyPrefix + "MessagePartType"];
+            switch (messagePartType.ToLower()) {
+                case "url":
+                    MessagePartType = typeof(UrlMessagePartModel);
+                    break;
+                case "image":
+                    MessagePartType = typeof(ImageMessagePartModel);
+                    break;
+            }
+            LinkFormat = (string) config[ConfigKeyPrefix + "LinkFormat"];
+            TextFormat = (string) config[ConfigKeyPrefix + "TextFormat"];
+        }
+
+        public virtual void Save(UserConfig config)
+        {
+            if (config == null) {
+                throw new ArgumentNullException("config");
+            }
+
+            if (MessagePartPattern == null) {
+                config[ConfigKeyPrefix + "MessagePartPattern"] = String.Empty;
+            } else {
+                config[ConfigKeyPrefix + "MessagePartPattern"] = MessagePartPattern.ToString();
+            }
+            if (MessagePartType == typeof(ImageMessagePartModel)) {
+                config[ConfigKeyPrefix + "MessagePartType"] = "Image";
+            } else if (MessagePartType == typeof(UrlMessagePartModel)) {
+                config[ConfigKeyPrefix + "MessagePartType"] = "Url";
+            } else {
+                config[ConfigKeyPrefix + "MessagePartType"] = String.Empty;
+            }
+            config[ConfigKeyPrefix + "LinkFormat"] = LinkFormat ?? String.Empty;
+            config[ConfigKeyPrefix + "TextFormat"] = TextFormat ?? String.Empty;
+        }
+
+        public override string ToString()
+        {
+            return String.Format("<{0}>", ToTraceString());
+        }
+
+        public string ToTraceString()
+        {
+            return String.Format("{0}", ID);
+        }
+    }
+}
diff --git a/src/Engine/Config/MessagePatternSettings.cs b/src/Engine/Config/MessagePatternSettings.cs
new file mode 100644
index 0000000..fc55e03
--- /dev/null
+++ b/src/Engine/Config/MessagePatternSettings.cs
@@ -0,0 +1,160 @@
+/*
+ * Smuxi - Smart MUltipleXed Irc
+ *
+ * Copyright (c) 2014 Mirco Bauer <meebey meebey net>
+ *
+ * Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+using System;
+using System.Collections.Generic;
+using Smuxi.Common;
+using Smuxi.Engine;
+
+namespace Smuxi.Engine
+{
+    public class MessagePatternSettings
+    {
+        UserConfig UserConfig { get; set; }
+
+        protected string[] PatternIDs {
+            get {
+                return (string[]) UserConfig["MessagePatterns/MessagePatterns"];
+            }
+            set {
+                UserConfig["MessagePatterns/MessagePatterns"] = value;
+            }
+        }
+
+        public MessagePatternSettings(UserConfig userConfig)
+        {
+            if (userConfig == null) {
+                throw new ArgumentNullException("userConfig");
+            }
+
+            UserConfig = userConfig;
+        }
+
+        public List<MessagePatternModel> GetList()
+        {
+            var keys = PatternIDs;
+            var list = new List<MessagePatternModel>(keys.Length);
+            if (keys == null) {
+                return list;
+            }
+            foreach (var key in keys) {
+                int parsedKey = Int32.Parse(key);
+                var link = Get(parsedKey);
+                if (link == null) {
+                    continue;
+                }
+                list.Add(link);
+            }
+            return list;
+        }
+
+        public MessagePatternModel Get(int id)
+        {
+            Trace.Call(id);
+
+            string prefix = "MessagePatterns/" + id + "/";
+            if (UserConfig[prefix + "MessagePartPattern"] == null) {
+                // link does not exist
+                return null;
+            }
+            var link = new MessagePatternModel(id);
+            link.Load(UserConfig);
+            return link;
+        }
+
+        public int Add(MessagePatternModel link)
+        {
+            return Add(link, -1);
+        }
+
+        public int Add(MessagePatternModel link, int id)
+        {
+            Trace.Call(link, id);
+
+            if (link == null) {
+                throw new ArgumentNullException("link");
+            }
+
+            string[] keys = PatternIDs;
+            if (keys == null) {
+                keys = new string[] {};
+            }
+            int highestKey = 0;
+            int newKey = id;
+            if (id == -1) {
+                foreach (string key in keys) {
+                    int parsedKey = Int32.Parse(key);
+                    if (parsedKey > highestKey) {
+                        highestKey = parsedKey;
+                    }
+                }
+                newKey = ++highestKey;
+            }
+
+            link.ID = newKey;
+            link.Save(UserConfig);
+
+            var keyList = new List<string>(keys);
+            keyList.Add(link.ID.ToString());
+            PatternIDs = keyList.ToArray();
+            return newKey;
+        }
+
+        public void Set(MessagePatternModel link)
+        {
+            Trace.Call(link);
+
+            if (link == null) {
+                throw new ArgumentNullException("link");
+            }
+
+            link.Save(UserConfig);
+        }
+
+        public void Remove(int key)
+        {
+            Trace.Call(key);
+
+            string section = "MessagePatterns/" + key + "/";
+            string[] keys = PatternIDs;
+            if (keys == null) {
+                keys = new string[] {};
+            }
+            var keyList = new List<string>(keys);
+            int idx = keyList.IndexOf(key.ToString());
+            if (idx == -1) {
+                // key not found
+                return;
+            }
+            keyList.RemoveAt(idx);
+            UserConfig.Remove(section);
+            PatternIDs = keyList.ToArray();
+        }
+
+        public void Save()
+        {
+            Trace.Call();
+
+            UserConfig.Save();
+        }
+    }
+}
diff --git a/src/Engine/Engine.csproj b/src/Engine/Engine.csproj
index 8bd8d98..37a6091 100644
--- a/src/Engine/Engine.csproj
+++ b/src/Engine/Engine.csproj
@@ -107,6 +107,8 @@
     <Compile Include="Hooks\Environments\CommandHookEnvironment.cs" />
     <Compile Include="Config\MessageBuilderSettings.cs" />
     <Compile Include="Hooks\Environments\PersonHookEnvironment.cs" />
+    <Compile Include="Config\MessagePatternModel.cs" />
+    <Compile Include="Config\MessagePatternSettings.cs" />
   </ItemGroup>
   <ItemGroup>
     <Folder Include="Protocols\" />
diff --git a/src/Engine/Makefile.am b/src/Engine/Makefile.am
index 1403715..99b0152 100644
--- a/src/Engine/Makefile.am
+++ b/src/Engine/Makefile.am
@@ -94,6 +94,8 @@ FILES = \
        Config/ProxyType.cs \
        Config/EntrySettings.cs \
        Config/MessageBuilderSettings.cs \
+       Config/MessagePatternModel.cs \
+       Config/MessagePatternSettings.cs \
        Protocols/ProtocolManagerBase.cs \
        Protocols/ProtocolManagerFactory.cs \
        Protocols/ProtocolManagerInfoModel.cs \
diff --git a/src/Engine/Messages/MessageBuilder.cs b/src/Engine/Messages/MessageBuilder.cs
index 49bce15..a944ecc 100644
--- a/src/Engine/Messages/MessageBuilder.cs
+++ b/src/Engine/Messages/MessageBuilder.cs
@@ -275,7 +275,7 @@ namespace Smuxi.Engine
 
         public virtual MessageBuilder AppendMessage(string msg)
         {
-            return Append(ParseSmartLinks(CreateText(msg)));
+            return Append(ParsePatterns(CreateText(msg)));
         }
 
         public  MessageBuilder AppendMessage(ContactModel sender, string msg)
@@ -856,82 +856,90 @@ namespace Smuxi.Engine
             return LibraryCatalog.GetString(msg, LibraryTextDomain);
         }
 
-        public static IList<MessagePartModel> ParseSmartLinks(TextMessagePartModel textPart,
-                                                              List<MessageBuilderSettings.SmartLink> links)
+        public static IList<MessagePartModel> ParsePatterns(TextMessagePartModel textPart,
+                                                            List<MessagePatternModel> patterns)
         {
+            if (textPart == null) {
+                throw new ArgumentNullException("textPart");
+            }
+            if (patterns == null) {
+                throw new ArgumentNullException("patterns");
+            }
+
             var msgParts = new List<MessagePartModel>();
-            if (links.Count == 0) {
-                // all smartlinks have been tried -> this text is PURE text
+            if (patterns.Count == 0) {
+                // all patterns have been tried -> this text is PURE text
                 msgParts.Add(textPart);
                 return msgParts;
             }
-            var subLinks = new List<MessageBuilderSettings.SmartLink>(links);
-            var link = subLinks.First();
-            subLinks.Remove(link);
+
+            var remainingPatterns = new List<MessagePatternModel>(patterns);
+            var pattern = remainingPatterns.First();
+            remainingPatterns.Remove(pattern);
             
-            var linkMatch = link.MessagePartPattern.Match(textPart.Text);
-            if (!linkMatch.Success) {
-                // no smartlinks in this MessagePart, try other smartlinks
-                return ParseSmartLinks(textPart, subLinks);
+            var match = pattern.MessagePartPattern.Match(textPart.Text);
+            if (!match.Success) {
+                // no matches in this MessagePart, try other smartlinks
+                return ParsePatterns(textPart, remainingPatterns);
             }
             
             int lastindex = 0;
             do {
-                var groupValues = new string[linkMatch.Groups.Count];
+                var groupValues = new string[match.Groups.Count];
                 int i = 0;
-                foreach (Group @group in linkMatch.Groups) {
+                foreach (Group @group in match.Groups) {
                     groupValues[i++] = @group.Value;
                 }
                 
                 string url;
-                if (String.IsNullOrEmpty(link.LinkFormat)) {
-                    url = linkMatch.Value;
+                if (String.IsNullOrEmpty(pattern.LinkFormat)) {
+                    url = match.Value;
                 } else {
-                    url = String.Format(link.LinkFormat, groupValues);
+                    url = String.Format(pattern.LinkFormat, groupValues);
                 }
                 string text;
-                if (String.IsNullOrEmpty(link.TextFormat)) {
-                    text = linkMatch.Value;
+                if (String.IsNullOrEmpty(pattern.TextFormat)) {
+                    text = match.Value;
                 } else {
-                    text = String.Format(link.TextFormat, groupValues);
+                    text = String.Format(pattern.TextFormat, groupValues);
                 }
 
-                if (lastindex != linkMatch.Index) {
-                    // there were some non-url-chars before this url
-                    // copy TextMessagePartModel
-                    var notLinkPart = new TextMessagePartModel(textPart);
+                if (lastindex != match.Index) {
+                    // there were some non-matching-chars before the match
+                    // copy that to a TextMessagePartModel
+                    var notMatchPart = new TextMessagePartModel(textPart);
                     // only take the proper chunk of text
-                    notLinkPart.Text = textPart.Text.Substring(lastindex, linkMatch.Index - lastindex);
-                    // and try other smartlinks on this part
-                    var parts = ParseSmartLinks(notLinkPart, subLinks);
+                    notMatchPart.Text = textPart.Text.Substring(lastindex, match.Index - lastindex);
+                    // and try other patterns on this part
+                    var parts = ParsePatterns(notMatchPart, remainingPatterns);
                     foreach (var part in parts) {
                         msgParts.Add(part);
                     }
                 }
                 
-                MessagePartModel model;
-                if (link.MessagePartType == typeof(UrlMessagePartModel)) {
+                MessagePartModel msgPart;
+                if (pattern.MessagePartType == typeof(UrlMessagePartModel)) {
                     // no need to set URL and text if they are the same
                     text = text == url ? null : text;
-                    model = new UrlMessagePartModel(url, text);
-                } else if (link.MessagePartType == typeof(ImageMessagePartModel)) {
-                    model = new ImageMessagePartModel(url, text);
+                    msgPart = new UrlMessagePartModel(url, text);
+                } else if (pattern.MessagePartType == typeof(ImageMessagePartModel)) {
+                    msgPart = new ImageMessagePartModel(url, text);
                 } else {
-                    model = new TextMessagePartModel(text);
+                    msgPart = new TextMessagePartModel(text);
                 }
-                msgParts.Add(model);
-                lastindex = linkMatch.Index + linkMatch.Length;
-                linkMatch = linkMatch.NextMatch();
-            } while (linkMatch.Success);
+                msgParts.Add(msgPart);
+                lastindex = match.Index + match.Length;
+                match = match.NextMatch();
+            } while (match.Success);
             
             if (lastindex != textPart.Text.Length) {
                 // there were some non-url-chars before this url
                 // copy TextMessagePartModel
-                TextMessagePartModel notLinkPart = new TextMessagePartModel(textPart);
+                var notMatchPart = new TextMessagePartModel(textPart);
                 // only take the proper chunk of text
-                notLinkPart.Text = textPart.Text.Substring(lastindex);
+                notMatchPart.Text = textPart.Text.Substring(lastindex);
                 // and try other smartlinks on this part
-                var parts = ParseSmartLinks(notLinkPart, subLinks);
+                var parts = ParsePatterns(notMatchPart, remainingPatterns);
                 foreach (var part in parts) {
                     msgParts.Add(part);
                 }
@@ -939,9 +947,9 @@ namespace Smuxi.Engine
             return msgParts;
         }
         
-        public IEnumerable<MessagePartModel> ParseSmartLinks(TextMessagePartModel part)
+        public IEnumerable<MessagePartModel> ParsePatterns(TextMessagePartModel part)
         {
-            return ParseSmartLinks(part, Settings.SmartLinks);
+            return ParsePatterns(part, Settings.Patterns);
         }
     }
 }
diff --git a/src/Engine/Session.cs b/src/Engine/Session.cs
index 7bebecd..80bcf13 100644
--- a/src/Engine/Session.cs
+++ b/src/Engine/Session.cs
@@ -154,6 +154,7 @@ namespace Smuxi.Engine
             _UserConfig.Changed += OnUserConfigChanged;
             _FilterListController = new FilterListController(_UserConfig);
             _Filters = _FilterListController.GetFilterList().Values;
+            MessageBuilderSettings.ApplyStaticConfig(_UserConfig);
             _Chats = new List<ChatModel>();
 
             InitSessionChat();
@@ -730,31 +731,50 @@ namespace Smuxi.Engine
                         return;
                     }
                     string setKey = setParam.Split('=')[0];
-                    string setValue = setParam.Split('=')[1];
+                    string setValue = String.Join(
+                        "=", setParam.Split('=').Skip(1).ToArray()
+                    );
                     object oldValue = _UserConfig[setKey];
+                    if (oldValue == null && setKey.StartsWith("MessagePatterns/")) {
+                        var id = setKey.Split('/')[1];
+                        var parsedId = Int32.Parse(id);
+                        var msgPatternSettings = new MessagePatternSettings(_UserConfig);
+                        var pattern = msgPatternSettings.Get(parsedId);
+                        if (pattern == null) {
+                            // pattern does not exist, create it with default values
+                            pattern = new MessagePatternModel(parsedId);
+                            msgPatternSettings.Add(pattern, parsedId);
+                            oldValue = _UserConfig[setKey];
+                        }
+                    }
                     if (oldValue == null) {
                         builder.AppendErrorText(
                             _("Invalid config key: '{0}'"),
                             setKey
                         );
-                    } else {
-                        try {
-                            object newValue = Convert.ChangeType(setValue, oldValue.GetType());
-                            _UserConfig[setKey] = newValue;
-                            builder.AppendText("{0} = {1}", setKey, newValue.ToString());
-                        } catch (InvalidCastException) {
-                            builder.AppendErrorText(
-                                _("Could not convert config value: '{0}' to type: {1}"),
-                                setValue,
-                                oldValue.GetType().Name
-                            );
-                        } catch (FormatException) {
-                            builder.AppendErrorText(
-                                _("Could not convert config value: '{0}' to type: {1}"),
-                                setValue,
-                                oldValue.GetType().Name
-                            );
+                        AddMessageToFrontend(cd, builder.ToMessage());
+                        return;
+                    }
+
+                    try {
+                        object newValue = Convert.ChangeType(setValue, oldValue.GetType());
+                        _UserConfig[setKey] = newValue;
+                        builder.AppendText("{0} = {1}", setKey, newValue.ToString());
+                        if (setKey.StartsWith("MessagePatterns/")) {
+                            MessageBuilderSettings.ApplyStaticConfig(UserConfig);
                         }
+                    } catch (InvalidCastException) {
+                        builder.AppendErrorText(
+                            _("Could not convert config value: '{0}' to type: {1}"),
+                            setValue,
+                            oldValue.GetType().Name
+                        );
+                    } catch (FormatException) {
+                        builder.AppendErrorText(
+                            _("Could not convert config value: '{0}' to type: {1}"),
+                            setValue,
+                            oldValue.GetType().Name
+                        );
                     }
                     break;
                 default:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]