1 /* 2 * Smuxi - Smart MUltipleXed Irc 3 * 4 * Copyright (c) 2005-2006, 2010-2011, 2013, 2015, 2017 Mirco Bauer <meebey@meebey.net> 5 * 6 * Full GPL License: <http://www.gnu.org/licenses/gpl.txt> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 */ 22 23 using System; 24 using System.Runtime.Remoting; 25 using System.Collections; 26 using System.Collections.Generic; 27 using Smuxi.Common; 28 29 namespace Smuxi.Engine 30 { 31 public class UserConfig : PermanentRemoteObject, IEnumerable<KeyValuePair<string, object>> 32 { 33 #if LOG4NET 34 private static readonly log4net.ILog _Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 35 #endif 36 private Config _Config; 37 private string _UserPrefix; 38 private string _DefaultPrefix = "Engine/Users/DEFAULT/"; 39 private Hashtable _Cache; 40 41 public FrontendConfig FrontendConfig { get; set; } 42 43 public event EventHandler<ConfigChangedEventArgs> Changed; 44 45 public bool IsCaching 46 { 47 get { 48 return _Cache != null; 49 } 50 set { 51 if (value) { 52 _Cache = new Hashtable(); 53 } else { 54 _Cache = null; 55 } 56 } 57 } 58 59 /// <remarks> 60 /// This property is thread-safe. 61 /// </remarks> 62 public object this[string key] 63 { 64 get { 65 object obj = null; 66 // allow engine config to be overriden by frontend config 67 if (FrontendConfig != null) { 68 obj = FrontendConfig[key]; 69 if (obj != null) { 70 return obj; 71 } 72 } 73 74 if (IsCaching) { 75 if (_Cache.Contains(key)) { 76 return _Cache[key]; 77 } 78 } 79 80 obj = _Config[_UserPrefix + key]; 81 if (obj != null) { 82 if (IsCaching) { 83 _Cache[key] = obj; 84 } 85 return obj; 86 } 87 88 obj = _Config[_DefaultPrefix + key]; 89 #if LOG4NET 90 if (obj == null) { 91 _Logger.Error("get_Item[]: default value is null for key: " + key); 92 } 93 #endif 94 if (IsCaching) { 95 _Cache[key] = obj; 96 } 97 98 return obj; 99 } 100 set { 101 Set(key, value); 102 } 103 } 104 UserConfig(Config config, string username)105 public UserConfig(Config config, string username) 106 { 107 _Config = config; 108 // HACK: The Changed event was introduced in 0.7.2, for backwards 109 // compatibility with 0.7.x server we need to suppress remoting 110 // exceptions here 111 try { 112 // we can't use events over remoting 113 if (!RemotingServices.IsTransparentProxy(config)) { 114 _Config.Changed += OnConfigChanged; 115 } 116 } catch (Exception ex) { 117 #if LOG4NET 118 _Logger.Warn( 119 "UserConfig() registration of Config.Changed event failed, " + 120 "ignoring for backwards compatibility with 0.7.x servers...", 121 ex 122 ); 123 #endif 124 } 125 _UserPrefix = "Engine/Users/"+username+"/"; 126 } 127 ClearCache()128 public void ClearCache() 129 { 130 if (IsCaching) { 131 #if LOG4NET 132 _Logger.Debug("Clearing cache"); 133 #endif 134 _Cache.Clear(); 135 } 136 } 137 Set(string key, object value)138 void Set(string key, object value) 139 { 140 if (key == null) { 141 throw new ArgumentNullException("key"); 142 } 143 if (value == null) { 144 throw new ArgumentNullException("value"); 145 } 146 147 // ignore writing back overridden config keys 148 if (FrontendConfig != null && FrontendConfig[key] != null) { 149 return; 150 } 151 152 // TODO: make remoting calls after a timeout and batch the update 153 _Config[_UserPrefix + key] = value; 154 155 // update entry in cache 156 if (IsCaching) { 157 _Cache[key] = value; 158 } 159 } 160 SetAll(IEnumerable<KeyValuePair<string, object>> settings)161 public void SetAll(IEnumerable<KeyValuePair<string, object>> settings) 162 { 163 if (settings == null) { 164 throw new ArgumentNullException("settings"); 165 } 166 167 var filteredSettings = new Dictionary<string, object>(); 168 foreach (var setting in settings) { 169 // ignore writing back overridden settings in the frontend config 170 if (FrontendConfig != null && FrontendConfig[setting.Key] != null) { 171 continue; 172 } 173 174 filteredSettings.Add(setting.Key, setting.Value); 175 // update entry in cache 176 if (IsCaching) { 177 _Cache[setting.Key] = setting.Value; 178 } 179 } 180 181 // REMOTING CALL 182 _Config.SetAll(filteredSettings); 183 } 184 Remove(string key)185 public void Remove(string key) 186 { 187 _Config.Remove(_UserPrefix + key); 188 189 if (!IsCaching) { 190 return; 191 } 192 if (key.EndsWith("/")) { 193 // invalidate all cache keys of this section 194 var cachedKeys = new List<string>(); 195 foreach (string cacheKey in _Cache.Keys) { 196 if (cacheKey.StartsWith(key)) { 197 cachedKeys.Add(cacheKey); 198 } 199 } 200 foreach (string cacheKey in cachedKeys) { 201 _Cache.Remove(cacheKey); 202 } 203 } else { 204 // deleting the single entry is enough 205 _Cache.Remove(key); 206 } 207 } 208 Save()209 public void Save() 210 { 211 _Config.Save(); 212 } 213 SyncCache()214 public void SyncCache() 215 { 216 Trace.Call(); 217 218 if (!IsCaching) { 219 return; 220 } 221 222 var start = DateTime.UtcNow; 223 var conf = _Config.GetAll(); 224 var cache = Hashtable.Synchronized(new Hashtable(conf.Count)); 225 foreach (var entry in conf) { 226 if (!entry.Key.StartsWith(_UserPrefix)) { 227 // no need to cache values of other users 228 continue; 229 } 230 // remove user prefix from key 231 var userKey = entry.Key.Substring(_UserPrefix.Length); 232 cache.Add(userKey, entry.Value); 233 } 234 var stop = DateTime.UtcNow; 235 #if LOG4NET 236 _Logger.Debug( 237 String.Format( 238 "SyncCache(): syncing config took: {0:0.00} ms", 239 (stop - start).TotalMilliseconds 240 ) 241 ); 242 #endif 243 _Cache = cache; 244 } 245 IEnumerable.GetEnumerator()246 IEnumerator IEnumerable.GetEnumerator() 247 { 248 return GetEnumerator(); 249 } 250 GetEnumerator()251 public IEnumerator<KeyValuePair<string, object>> GetEnumerator() 252 { 253 foreach (var entry in _Config.GetAll()) { 254 if (!entry.Key.StartsWith(_UserPrefix)) { 255 continue; 256 } 257 // remove user prefix from key 258 var userKey = entry.Key.Substring(_UserPrefix.Length); 259 yield return new KeyValuePair<string, object>(userKey, entry.Value); 260 } 261 } 262 OnConfigChanged(object sender, ConfigChangedEventArgs e)263 void OnConfigChanged(object sender, ConfigChangedEventArgs e) 264 { 265 if (Changed == null) { 266 // no listeners 267 return; 268 } 269 270 if (!e.Key.StartsWith(_UserPrefix)) { 271 // setting for some other user has changed 272 return; 273 } 274 275 var key = e.Key.Substring(_UserPrefix.Length); 276 Changed(this, new ConfigChangedEventArgs(key, e.Value)); 277 } 278 } 279 } 280