2005-12-02 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / corlib / Microsoft.Win32 / UnixRegistryApi.cs
1 //
2 // Microsoft.Win32/IRegistryApi.cs
3 //
4 // Authors:
5 //      Miguel de Icaza (miguel@gnome.org)
6 //
7 // (C) 2005 Novell, Inc (http://www.novell.com)
8 // 
9 // MISSING:
10 //   Someone could the same subkey twice: once read/write once readonly,
11 //   currently since we use a unique hash based on the file name, we are unable
12 //   to have two versions of the same key and hence unable to throw an exception
13 //   if the user tries to write to a read-only key.
14 //
15 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 // 
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 // 
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36
37 using System;
38 using System.Collections;
39 using System.IO;
40 using System.Text;
41 using System.Runtime.InteropServices;
42 using System.Reflection;
43 using System.Security;
44 using System.Threading;
45
46 namespace Microsoft.Win32 {
47
48         class KeyHandler {
49                 static Hashtable key_to_handler = new Hashtable ();
50                 static Hashtable dir_to_key = new Hashtable ();
51                 public string Dir;
52                 public IntPtr Handle;
53
54                 public Hashtable values;
55                 string file;
56                 bool dirty;
57                 
58                 KeyHandler (RegistryKey rkey, string basedir)
59                 {
60                         if (!Directory.Exists (basedir)){
61                                 try {
62                                         Directory.CreateDirectory (basedir);
63                                 } catch (Exception e){
64                                         Console.Error.WriteLine ("KeyHandler error while creating directory {0}:\n{1}", basedir, e);
65                                 }
66                         }
67                         Dir = basedir;
68                         file = Path.Combine (Dir, "values.xml");
69                         Load ();
70                 }
71
72                 public void Load ()
73                 {
74                         values = new Hashtable ();
75                         if (!File.Exists (file))
76                                 return;
77                         
78                         try {
79                                 using (FileStream fs = File.OpenRead (file)){
80                                         StreamReader r = new StreamReader (fs);
81                                         string xml = r.ReadToEnd ();
82                                         if (xml.Length == 0)
83                                                 return;
84                                         
85                                         SecurityElement tree = SecurityElement.FromString (xml);
86                                         if (tree.Tag == "values"){
87                                                 foreach (SecurityElement value in tree.Children){
88                                                         if (value.Tag == "value"){
89                                                                 LoadKey (value);
90                                                         }
91                                                 }
92                                         }
93                                 }
94                         } catch (Exception e){
95                                 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
96                                 values.Clear ();
97                         }
98                 }
99
100                 void LoadKey (SecurityElement se)
101                 {
102                         Hashtable h = se.Attributes;
103                         try {
104                                 string name = (string) h ["name"];
105                                 if (name == null)
106                                         return;
107                                 string type = (string) h ["type"];
108                                 if (type == null)
109                                         return;
110                                 
111                                 switch (type){
112                                 case "int":
113                                         values [name] = Int32.Parse (se.Text);
114                                         break;
115                                 case "bytearray":
116                                         Convert.FromBase64String (se.Text);
117                                         break;
118                                 case "string":
119                                         values [name] = se.Text;
120                                         break;
121                                 case "string-array":
122                                         ArrayList sa = new ArrayList ();
123                                         if (se.Children != null){
124                                                 foreach (SecurityElement stre in se.Children){
125                                                         sa.Add (stre.Text);
126                                                 }
127                                         }
128                                         values [name] = sa.ToArray (typeof (string));
129                                         break;
130                                 }
131                         } catch {
132                                 // We ignore individual errors in the file.
133                         }
134                 }
135                 
136                 public RegistryKey Ensure (RegistryKey rkey, string extra)
137                 {
138                         lock (typeof (KeyHandler)){
139                                 string f = Path.Combine (Dir, extra);
140                                 if (dir_to_key.Contains (f))
141                                         return (RegistryKey) dir_to_key [f];
142
143                                 KeyHandler kh = new KeyHandler (rkey, f);
144                                 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra));
145                                 key_to_handler [rk] = kh;
146                                 dir_to_key [f] = rk;
147                                 return rk;
148                         }
149                 }
150
151                 public RegistryKey Probe (RegistryKey rkey, string extra, bool write)
152                 {
153                         lock (typeof (KeyHandler)){
154                                 string f = Path.Combine (Dir, extra);
155                                 if (dir_to_key.Contains (f))
156                                         return (RegistryKey) dir_to_key [f];
157                                 Console.WriteLine ("Trying: " + f);
158                                 if (Directory.Exists (f)){
159                                         KeyHandler kh = new KeyHandler (rkey, f);
160                                         RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra));
161                                         dir_to_key [f] = rk;
162                                         key_to_handler [rk] = kh;
163                                         return rk;
164                                 }
165                                 return null;
166                         }
167                 }
168
169                 static string CombineName (RegistryKey rkey, string extra)
170                 {
171                         if (extra.IndexOf ('/') != -1)
172                                 extra = extra.Replace ('/', '\\');
173                         
174                         return String.Concat (rkey.Name, "\\", extra);
175                 }
176                 
177                 public static KeyHandler Lookup (RegistryKey rkey)
178                 {
179                         lock (typeof (KeyHandler)){
180                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
181                                 if (k != null)
182                                         return k;
183
184                                 RegistryHive x = (RegistryHive) rkey.Data;
185                                 switch (x){
186                                 case RegistryHive.ClassesRoot:
187                                 case RegistryHive.CurrentConfig:
188                                 case RegistryHive.CurrentUser:
189                                 case RegistryHive.DynData:
190                                 case RegistryHive.LocalMachine:
191                                 case RegistryHive.PerformanceData:
192                                 case RegistryHive.Users:
193                                         string d = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), ".mono/registry");
194                                         d = Path.Combine (d, x.ToString ());
195                                         
196                                         k = new KeyHandler (rkey, d);
197                                         key_to_handler [rkey] = k;
198                                         break;
199                                 default:
200                                         throw new Exception ("Unknown RegistryHive");
201                                 }
202                                 key_to_handler [rkey] = k;
203                                 return k;
204                         }
205                 }
206
207                 public static void Drop (RegistryKey rkey)
208                 {
209                         KeyHandler k = (KeyHandler) key_to_handler [rkey];
210                         if (k == null)
211                                 return;
212                         dir_to_key.Remove (k.Dir);
213                         key_to_handler.Remove (rkey);
214                 }
215
216                 public static void Drop (string dir)
217                 {
218                         if (dir_to_key.Contains (dir)){
219                                 key_to_handler.Remove (dir_to_key [dir]); 
220                                 dir_to_key.Remove (dir);
221                         }
222                 }
223
224                 public void SetValue (string name, object value)
225                 {
226                         values [name] = value;
227                         SetDirty ();
228                 }
229
230                 void SetDirty ()
231                 {
232                         lock (typeof (KeyHandler)){
233                                 if (dirty)
234                                         return;
235                                 dirty = true;
236                                 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
237                         }
238                 }
239
240                 public void DirtyTimeout (object state)
241                 {
242                         Flush ();
243                 }
244                 
245                 public void Flush ()
246                 {
247                         lock (typeof (KeyHandler)){
248                                 dirty = false;
249                                 Save ();
250                         }
251                 }
252
253                 ~KeyHandler ()
254                 {
255                         Flush ();
256                 }
257                 
258                 void Save ()
259                 {
260                         if (!File.Exists (file) && values.Count == 0)
261                                 return;
262                         
263                         SecurityElement se = new SecurityElement ("values");
264                         
265                         foreach (DictionaryEntry de in values){
266                                 object val = de.Value;
267                                 SecurityElement value = new SecurityElement ("value");
268                                 value.AddAttribute ("name", (string) de.Key);
269                                 
270                                 if (val is string){
271                                         value.AddAttribute ("type", "string");
272                                         value.Text = (string) val;
273                                 } else if (val is int){
274                                         value.AddAttribute ("type", "int");
275                                         value.Text = val.ToString ();
276                                 } else if (val is byte []){
277                                         value.AddAttribute ("type", "bytearray");
278                                         value.Text = Convert.ToBase64String ((byte[]) val);
279                                 } else if (val is string []){
280                                         value.AddAttribute ("type", "string-array");
281
282                                         foreach (string ss in (string[]) val){
283                                                 SecurityElement str = new SecurityElement ("string");
284                                                 str.Text = ss; 
285                                                 value.AddChild (str);
286                                         }
287                                 }
288                                 se.AddChild (value);
289                         }
290
291                         try {
292                                 using (FileStream fs = File.Create (file)){
293                                         StreamWriter sw = new StreamWriter (fs);
294
295                                         sw.Write (se.ToString ());
296                                         sw.Flush ();
297                                 }
298                         } catch (Exception e){
299                                 Console.Error.WriteLine ("When saving {0} got {1}", file, e);
300                         }
301                 }
302         }
303         
304         internal class UnixRegistryApi : IRegistryApi {
305
306                 static string ToUnix (string keyname)
307                 {
308                         if (keyname.IndexOf ('\\') != -1)
309                                 keyname = keyname.Replace ('\\', '/');
310                         return keyname.ToLower ();
311                 }
312                 
313                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
314                 {
315                         KeyHandler self = KeyHandler.Lookup (rkey);
316                         return self.Ensure (rkey, ToUnix (keyname));
317                 }
318
319                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writtable)
320                 {
321                         KeyHandler self = KeyHandler.Lookup (rkey);
322                         return self.Probe (rkey, ToUnix (keyname), writtable);
323                 }
324                 
325                 public void Flush (RegistryKey rkey)
326                 {
327                         KeyHandler self = KeyHandler.Lookup (rkey);
328                         self.Flush ();
329                 }
330                 
331                 public void Close (RegistryKey rkey)
332                 {
333                         KeyHandler.Drop (rkey);
334                 }
335                 
336                 public object GetValue (RegistryKey rkey, string name, bool return_default_value, object default_value)
337                 {
338                         KeyHandler self = KeyHandler.Lookup (rkey);
339
340                         if (self.values.Contains (name))
341                                 return self.values [name];
342                         if (return_default_value)
343                                 return default_value;
344                         return null;
345                 }
346                 
347                 public void SetValue (RegistryKey rkey, string name, object value)
348                 {
349                         if (!((value is int) || (value is string) || (value is string []) || (value is byte [])))
350                                 throw new ArgumentException ("The value is not int, string, string[] or byte[]", "value");
351                         
352                         KeyHandler self = KeyHandler.Lookup (rkey);
353                         self.SetValue (name, value);
354                 }
355
356                 public int SubKeyCount (RegistryKey rkey)
357                 {
358                         KeyHandler self = KeyHandler.Lookup (rkey);
359
360                         return Directory.GetDirectories (self.Dir).Length;
361                 }
362                 
363                 public int ValueCount (RegistryKey rkey)
364                 {
365                         KeyHandler self = KeyHandler.Lookup (rkey);
366
367                         return self.values.Keys.Count;
368                 }
369                 
370                 public void DeleteValue (RegistryKey rkey, string value, bool throw_if_missing)
371                 {
372                         KeyHandler self = KeyHandler.Lookup (rkey);
373
374                         foreach (DictionaryEntry de in self.values){
375                                 if ((string)de.Value == value){
376                                         self.values.Remove (de.Key);
377                                         return;
378                                 }
379                         }
380                         if (throw_if_missing)
381                                 throw new ArgumentException ("the given value does not exist", "value");
382                 }
383                 
384                 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
385                 {
386                         KeyHandler self = KeyHandler.Lookup (rkey);
387                         string dir = Path.Combine (self.Dir, keyname);
388                         
389                         if (Directory.Exists (dir)){
390                                 Directory.Delete (dir, true);
391                                 KeyHandler.Drop (dir);
392                         } else if (throw_if_missing)
393                                 throw new ArgumentException ("the given value does not exist", "value");
394                 }
395                 
396                 public string [] GetSubKeyNames (RegistryKey rkey)
397                 {
398                         KeyHandler self = KeyHandler.Lookup (rkey);
399                         return Directory.GetDirectories (self.Dir);
400                 }
401                 
402                 public string [] GetValueNames (RegistryKey rkey)
403                 {
404                         KeyHandler self = KeyHandler.Lookup (rkey);
405                         ICollection keys = self.values.Keys;
406
407                         string [] vals = new string [keys.Count];
408                         keys.CopyTo (vals, 0);
409                         return vals;
410                 }
411
412                 public string ToString (RegistryKey rkey)
413                 {
414                         return rkey.Name;
415                 }
416         }
417 }