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