*** empty log message ***
[m17n/m17n-lib-cs.git] / MDatabase.cs
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using M17N;
5 using M17N.Core;
6
7 namespace M17N.Core
8 {
9   internal class MDatabaseDir
10   {
11     private const string ListFileName = "mdb.dir";
12
13     public string Dirname;
14     public DirectoryInfo DirInfo;
15     public FileInfo ListInfo;
16     public DateTime LastScanned;
17
18     public MDatabaseDir (string dirname)
19     {
20       Dirname = dirname;
21       if (dirname != null)
22         {
23           try {
24             DirInfo = new DirectoryInfo (dirname);
25             try {
26               ListInfo = DirInfo.GetFiles (ListFileName)[0];
27             } catch {
28               ListInfo = null;
29             }
30           } catch {
31             DirInfo = null;
32             ListInfo = null;
33           }
34         }
35     }
36
37     public bool CheckStatus ()
38     {
39       if (DirInfo != null)
40         {
41           try {
42             DirInfo.Refresh ();
43           } catch {
44             DirInfo = null;
45             ListInfo = null;
46             LastScanned = new DateTime (0);
47             return true;
48           }
49           if (ListInfo != null)
50             try {
51               ListInfo.Refresh ();
52             } catch {
53               ListInfo = null;
54               return true;
55             }
56           else
57             {
58               try {
59                 ListInfo = DirInfo.GetFiles (ListFileName)[0];
60                 return true;
61               } catch {
62                 ListInfo = null;
63               }
64             }
65           return (LastScanned < DirInfo.LastWriteTime
66                   || (ListInfo != null
67                       && LastScanned < ListInfo.LastWriteTime));
68         }
69       else
70         {
71           if (Dirname != null && Directory.Exists (Dirname))
72             {
73               DirInfo = new DirectoryInfo (Dirname);
74               try {
75                 ListInfo = DirInfo.GetFiles (ListFileName)[0];
76               } catch {
77                 ListInfo = null;
78               }
79               return true;
80             }
81           return false;
82         }
83     }
84
85     public void UpdateStatus ()
86     {
87       if (DirInfo != null)
88         LastScanned = DateTime.UtcNow;
89     }
90
91     public FileInfo[] Scan (string filename)
92     {
93       if (DirInfo == null)
94         return null;
95       DirInfo.Refresh ();
96       return DirInfo.GetFiles (filename);
97     }
98   }
99
100   public class MDatabase
101   {
102     /// Tags to identify a MDatabase.
103     public struct Tag
104     {
105       public MSymbol[] Tags;
106
107       public Tag (MSymbol tag0)
108         {
109           Tags = new MSymbol[4];
110           Tags[0] = tag0; Tags[1] = Tags[2] = Tags[3] = MSymbol.nil;
111         }
112
113       public Tag (MSymbol tag0, MSymbol tag1)
114         {
115           Tags = new MSymbol[4];
116           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = Tags[3] = MSymbol.nil;
117         }
118
119       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2)
120         {
121           Tags = new MSymbol[4];
122           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = MSymbol.nil;
123         }
124
125       public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3)
126         {
127           Tags = new MSymbol[4];
128           Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = tag3;
129         }
130
131       public bool Match (Tag tag)
132       {
133         for (int i = 0; i < 4; i++)
134           {
135             if (tag.Tags[i] == Mwildcard || Tags[i] == Mwildcard)
136               return true;
137             if (tag.Tags[i] != Tags[i])
138               return false;
139           }
140         return true;
141       }
142
143       public override string ToString ()
144       {
145         return ("<"
146                 + Tags[0] + "," + Tags[1] + "," + Tags[2] + "," + Tags[3]
147                 + ">");
148       }
149     }
150
151     public delegate object Loader (Tag tag, object extra_info);
152
153     internal class MDatabaseInfo {
154       // -2: absolute, -1: unknown 0: DBDirs[0], 1: DBDirs[1], 2: DBDirs[2]
155       internal int DirIndex;
156       internal string Description;
157       internal string Filename;
158       internal FileInfo FileInfo;
159       internal FileInfo Validater;
160       internal int Version;
161       internal MSymbol Format;
162       internal MSymbol Schema;
163       internal string SchemaFile;
164       internal DateTime ModifiedTime;
165       internal MPlist Props;
166
167       public MDatabaseInfo ()
168       {
169         Format = Schema = MSymbol.nil;
170         DirIndex = -1;
171       }
172
173       public override string ToString ()
174       {
175         string str = ("#<Info " + Format + " \"" + Filename
176                       + "\" (" + DirIndex + ")");
177         if (Schema != MSymbol.nil)
178           str += " " + Schema;
179         return str + ">";
180       }
181     }
182
183     private static Dictionary<MDatabase.Tag, MDatabase[]> DBDict
184       = new Dictionary<MDatabase.Tag, MDatabase[]> ();
185
186     private static Dictionary<MDatabase.Tag, MDatabase[]> DBDictMulti
187       = new Dictionary<MDatabase.Tag, MDatabase[]> ();
188
189     private static MDatabaseDir[] DBDirs = new MDatabaseDir[3];
190
191     private static readonly MSymbol Mversion = MSymbol.Of ("version");
192
193     private static readonly MSymbol Mwildcard = MSymbol.Of ("*");
194     private static readonly MSymbol Mchar_table = MSymbol.Of ("char-table");
195     private static readonly MSymbol Mcharset = MSymbol.Of ("charset");
196
197     /// Type of database
198     private enum MDBType
199       {
200         /// The database was defined automatically from one of mdb.dir
201         /// files with no wildcard tag.
202         AUTO,
203         /// The database was defined automatically from one of mdb.dir
204         /// files with a wildcard tag to define multiple databases
205         /// of the same kind.
206         MULTIPLE,
207         /// The database was defined explicitely by MDatabase.Define
208         /// to use the normal loader.
209         EXPLICIT,
210         /// The database was defined explicitely by MDatabase.Define
211         /// to use a special loader.
212         UNKNOWN,
213         /// The database is for defining multiple databases of the
214         /// same kind with a wildcard tag.
215         WILDCARD,
216       };
217
218     /// Status of database
219     private enum MDBStatus
220       {
221         // The database file is currently disabled.  It means that the
222         // database file is not readable or the database is deleted by
223         // the modification of "mdb.dir".
224         DISABLED,
225         // The database file has not yet been loaded, or was modified
226         // after the previous loading.
227         OUTDATED,
228         // The database file has not been modified after the previous
229         // loading.
230         UPDATED,
231         // The database file is updated but the validation was failed
232         // or the version is not supported by the current system.
233         INVALID,
234       };
235
236     public Tag tag;
237     private Loader loader;
238     private object ExtraInfo;
239     private MDBType DBType;
240     private MDBStatus DBStatus;
241     internal DateTime LoadedTime;
242     internal MDatabaseInfo Info;
243
244     static MDatabase ()
245     {
246       string share_dir = (Environment.GetFolderPath
247                           (Environment.SpecialFolder.CommonApplicationData));
248       string usr_dir = (Environment.GetFolderPath
249                         (Environment.SpecialFolder.ApplicationData));
250
251       try {
252         DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d"));
253       } catch (ArgumentException) {
254         DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d"));
255       }
256       DBDirs[1] = new MDatabaseDir (null);
257       DBDirs[2] = new MDatabaseDir (Path.Combine (share_dir, "m17n"));
258     }
259
260     public static string ApplicationDir
261     { get { return (DBDirs[1].Dirname); }
262       set { DBDirs[1].Dirname = value; DBDirs[1].CheckStatus (); } }
263
264     private static bool update_database_directories ()
265     {
266       bool updated = false;
267       for (int i = 0; i < 3; i++)
268         if (DBDirs[i].CheckStatus ())
269           {
270             delete_databases (i);
271             if (DBDirs[i].ListInfo != null)
272               update_databases (i);
273             updated = true;
274           }
275       return updated;
276     }
277
278     public static void ListDirs ()
279     {
280       update_database_directories ();
281       for (int i = 0; i < 3; i++)
282         if (DBDirs[i].Dirname != null)
283           {
284             Console.Write ("{0}:{1}", i, DBDirs[i].Dirname);
285             if (DBDirs[i].DirInfo != null)
286               {
287                 if (DBDirs[i].ListInfo != null)
288                   Console.WriteLine (" {0}", DBDirs[i].ListInfo);
289                 else
290                   Console.WriteLine (".. no mdb.dir");
291               }
292             else
293               Console.WriteLine (".. not exist");
294           }
295     }
296
297     private void register (int priority)
298     {
299       Dictionary<MDatabase.Tag, MDatabase[]> dict
300         = DBType == MDBType.WILDCARD ? DBDictMulti : DBDict;
301       MDatabase[] mdbs;
302
303       if (! dict.TryGetValue (tag, out mdbs))
304         {
305           mdbs = new MDatabase[4];
306           dict.Add (tag, mdbs);
307         }
308       mdbs[priority] = this;
309     }
310
311     public MDatabase (Tag tag, Loader loader, object extra_info)
312     {
313       this.tag = tag;
314       this.loader = loader;
315       DBType = MDBType.UNKNOWN;
316       DBStatus = MDBStatus.UPDATED;
317       ExtraInfo = extra_info;
318       this.register (0);
319     }
320
321     public MDatabase (Tag tag, string filename)
322     {
323       this.tag = tag;
324       DBType = MDBType.EXPLICIT;
325       DBStatus = MDBStatus.OUTDATED;
326       Info = new MDatabaseInfo ();
327       Info.Filename = filename;
328       Info.DirIndex = Path.IsPathRooted (filename) ? -2 : -1;
329       this.register (0);
330     }
331
332     private MDatabase (MPlist plist, int priority)
333     {
334       tag = new Tag (MSymbol.nil);
335       DBType = MDBType.AUTO;
336       DBStatus = MDBStatus.OUTDATED;
337       for (int i = 0; plist.IsSymbol; i++, plist = plist.Next)
338         {
339           if (DBType == MDBType.WILDCARD)
340             tag.Tags[i] = MSymbol.nil;      
341           else
342             {
343               tag.Tags[i] = plist.Symbol;
344               if (tag.Tags[i] == Mwildcard)
345                 DBType = MDBType.WILDCARD;
346             }
347         }
348
349       Info = new MDatabaseInfo ();
350       if (tag.Tags[0] == Mchar_table || tag.Tags[0] == Mcharset)
351         Info.Format = tag.Tags[0];
352       else
353         Info.Format = MSymbol.plist;
354       if (plist.IsMText)
355         {
356           Info.Filename = plist.Text.ToString ();
357           plist = plist.Next;
358         }
359       else if (plist.IsPlist)
360         {
361           MPlist p = plist.Plist;
362
363           if (p.IsMText)
364             Info.Filename = plist.Text.ToString ();
365           p = p.Next;
366           if (! p.IsEmpty)
367             {
368               if (p.IsSymbol)
369                 Info.Format = p.Symbol;
370               p = p.Next;
371               if (! p.IsEmpty)
372                 {
373                   if (p.IsSymbol)
374                     Info.Schema = p.Symbol;
375                   p = p.Next;
376                   if (p.IsMText)
377                     Info.SchemaFile = p.Text.ToString ();
378                 }                   
379             }
380           plist = plist.Next;
381         }
382       else
383         throw new Exception ("Invalid source definition:" + plist);     
384
385       Info.DirIndex = Path.IsPathRooted (Info.Filename) ? -2 : -1;
386       Info.Version = 0;
387       Info.Props = new MPlist ();
388       foreach (MPlist pl in plist)
389         {
390           if (pl.IsPlist)
391             {
392               MPlist p = pl.Plist;
393               
394               if (p.IsSymbol && p.Symbol == Mversion)
395                 {
396                   Info.Version = parse_version (p.Next);
397                   if (M17n.Version < Info.Version)
398                     DBStatus = MDBStatus.DISABLED;
399                 }
400             }
401           Info.Props.Put (pl.Key, pl.Val);
402         }
403       this.register (priority);
404     }
405
406     public override String ToString () {
407       string str = "#<MDataBase " + tag + " " + DBType + " " + DBStatus;
408
409       if (DBType != MDBType.EXPLICIT && DBType != MDBType.UNKNOWN)
410         str += " " + Info;
411       return str + ">";
412     }
413
414     private static int parse_version (MPlist plist)
415     {
416       string[] str;
417       int major, minor, release;
418
419       if (! plist.IsMText)
420         return 0xFFFFFF;
421       str = plist.Text.ToString ().Split ('.');
422       if (str.Length != 3)
423         return 0xFFFFFF;
424       try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; }
425       try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; }
426       try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; }
427       return ((major << 16) | (minor << 8) | release);
428     }
429
430     private static void delete_databases (int list_idx)
431     {
432       foreach (KeyValuePair<Tag, MDatabase[]> item in DBDict)
433         item.Value[list_idx + 1] = null;
434       foreach (KeyValuePair<Tag, MDatabase[]> item in DBDictMulti)
435         item.Value[list_idx + 1] = null;
436     }
437
438     private static void update_databases (int list_idx)
439     {
440       MDatabaseDir dbdir = DBDirs[list_idx];
441       FileInfo dblist = dbdir.ListInfo;
442       MPlist plist = null;
443
444       using (FileStream stream = File.OpenRead (dblist.FullName))
445         {
446           MStreamReader reader = new MStreamReader (stream);
447           plist = new MPlist (reader);
448         }
449       if (plist == null)
450         return;
451       foreach (MPlist pl in plist)
452         {
453           if (! pl.IsPlist)
454             continue;
455           try
456             {
457               new MDatabase (pl.Plist, list_idx + 1);
458             }
459           catch (Exception e)
460             {
461               Console.WriteLine (e.Message);
462             }
463         }
464     }
465
466     private void expand_wildcard (int priority)
467     {
468       if (Info.DirIndex == -2)
469         {
470           
471         }
472
473     }
474
475     private void update_status ()
476     {
477       if (Info.DirIndex == -2)
478         {
479         }
480     }
481
482     public static MDatabase Find (Tag tag)
483     {
484       MDatabase[] mdbs;
485       MDatabase mdb = null;
486
487       if (DBDict.TryGetValue (tag, out mdbs))
488         {
489           if (mdbs[0] != null)
490             return mdbs[0];
491           for (int i = 1; i < 4; i++)
492             if ((mdb = mdbs[i]) != null)
493               break;
494         }
495       if (! update_database_directories ())
496         return mdb;
497       if (! DBDict.TryGetValue (tag, out mdbs))
498         return null;
499       for (int i = 1; i < 4; i++)
500         if (mdbs[i] != null)
501           return mdbs[i];
502       return null;
503     }
504
505     public static List<MDatabase> List (Tag tag)
506     {
507       List<MDatabase> list = new List<MDatabase> ();
508
509       update_database_directories ();
510       foreach (KeyValuePair<Tag, MDatabase[]> item in DBDictMulti)
511         if (item.Key.Match (tag))
512           for (j = 0; j < 4; j++)
513             if (item.Value[j] != null)
514               if (item.Value[j].DBStatus == MDBStatus.OUTDATED)
515                 item.Value[j].expand_wildcard (j);
516
517       for (int i = 0; i < 4 && tag.Tags[i] == Mwildcard; i++);
518       if (i == 4)
519         {
520           // No wildcard.
521           if (DBDict.TryGetValue (tag, out mdbs))
522             for (int j = 0; j < 4; j++)
523               if (mdbs[j] != null)
524                 {
525                   mdbs[j].update_status ();
526                   if (mdbs[j].DBStatus != MDBStatus.DISABLED)
527                     {
528                       list.Add (mdbs[j]);
529                       break;
530                     }
531                 }
532         }
533       else
534         {
535           // With wildcard.  We must scan all databases.
536           foreach (KeyValuePair<Tag, MDatabase[]> item in DBDict)
537             if (item.Key.Match (tag))
538               for (int j = 0; j < 4; j++)
539                 if (item.Value[j] != null)
540                   {
541                     MDatabase mdb = item.Value[j];
542                     mdb.update_status ();
543                     if (mdb.DBStatus != MDBStatus.DISABLED)
544                       {
545                         list.Add (mdb);
546                         break;
547                       }
548                   }
549         }
550       return list;
551     }
552
553     public object Load ()
554     {
555       return (loader != null ? loader (tag, ExtraInfo)
556               : load (MSymbol.nil, MSymbol.nil));
557     }
558
559     public object Load (MSymbol key, MSymbol stop)
560     {
561       if (loader != null)
562         return null;
563       return load (key, stop);
564     }
565
566     private object load (MSymbol key, MSymbol stop)
567     {
568       LoadedTime = DateTime.UtcNow;
569
570       return null;
571     }
572   }
573 }