using System; using System.Collections; using System.Collections.Generic; using System.IO; using M17N; using M17N.Core; namespace M17N.Core { internal class MGlob { static readonly char sep = Path.DirectorySeparatorChar; public static void FileList (ref List files, string dir, string pattern) { int len = pattern.Length; if (Path.IsPathRooted (pattern)) { int headsep = 0; int i; for (i = 1; i < len && (pattern[i] != '*' && pattern[i] != '?'); i++) if (pattern[i] == sep) headsep = i; if (i == len) { if (File.Exists (pattern)) files.Add (new FileInfo (pattern)); return; } dir = pattern.Substring (0, headsep); pattern = pattern.Substring (headsep + 1); } else { if (dir == null) dir = Directory.GetCurrentDirectory (); } if (Directory.Exists (dir)) list (ref files, new DirectoryInfo (dir), pattern); } private static void list (ref List files, DirectoryInfo dirinfo, string pattern) { int len = pattern.Length; int i; for (i = 0; i < len && pattern[i] != sep; i++); try { if (i == len) { FileInfo[] listing = dirinfo.GetFiles (pattern); foreach (FileInfo elt in listing) files.Add (elt); } else { string tail = pattern.Substring (i + 1); pattern = pattern.Substring (0, i); DirectoryInfo[] listing = dirinfo.GetDirectories (pattern); foreach (DirectoryInfo elt in listing) list (ref files, elt, tail); } } catch { } } } internal class MDatabaseDir { private const string ListFileName = "mdb.dir"; public string Dirname; public DirectoryInfo DirInfo; public DateTime DirChangeTime; public FileInfo ListInfo; public DateTime ListChangeTime; public MDatabaseDir (string dirname) { Dirname = dirname; DirChangeTime = ListChangeTime = DateTime.Now; } public void Refresh () { if (DirInfo != null) { if (Dirname != null && Directory.Exists (Dirname)) { DirInfo.Refresh (); if (DirChangeTime < DirInfo.LastWriteTime) DirChangeTime = DirInfo.LastWriteTime; } else { DirInfo = null; DirChangeTime = DateTime.Now; } } else { if (Dirname != null && Directory.Exists (Dirname)) { DirInfo = new DirectoryInfo (Dirname); DirChangeTime = DateTime.Now; } } if (DirInfo == null) { if (ListInfo != null) { ListInfo = null; ListChangeTime = DateTime.Now; } } else { if (ListInfo != null) { ListInfo.Refresh (); if (ListChangeTime < ListInfo.LastWriteTime) ListChangeTime = ListInfo.LastWriteTime; } else { try { ListInfo = DirInfo.GetFiles (ListFileName)[0]; ListChangeTime = DateTime.Now; } catch { } } } } } public partial class MDatabase : IComparable { /// Identifier of a MDatabase. public struct Tag : IEquatable { private MSymbol[] Tags; public Tag (MSymbol tag0) { Tags = new MSymbol[4]; Tags[0] = tag0; Tags[1] = Tags[2] = Tags[3] = MSymbol.nil; } public Tag (MSymbol tag0, MSymbol tag1) { Tags = new MSymbol[4]; Tags[0] = tag0; Tags[1] = tag1; Tags[2] = Tags[3] = MSymbol.nil; } public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2) { Tags = new MSymbol[4]; Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = MSymbol.nil; } public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3) { Tags = new MSymbol[4]; Tags[0] = tag0; Tags[1] = tag1; Tags[2] = tag2; Tags[3] = tag3; } public Tag (ref MPlist plist) { Tags = new MSymbol[4]; for (int i = 0; i < 4; i++) { if (plist.IsSymbol) { Tags[i] = plist.Symbol; plist = plist.Next; } else Tags[i] = MSymbol.nil; } } public bool Equals (Tag tag) { for (int i = 0; i < 4; i++) if (tag[i] != Tags[i]) return false; return true; } public override int GetHashCode () { return (Tags[0].GetHashCode () ^ Tags[1].GetHashCode () ^ Tags[2].GetHashCode () ^ Tags[3].GetHashCode ()); } public override string ToString () { return ("<" + Tags[0] + "," + Tags[1] + "," + Tags[2] + "," + Tags[3] + ">"); } public MSymbol this[int i] { set { Tags[i] = value; } get { return Tags[i]; } } public bool Match (Tag tag) { for (int i = 0; i < 4; i++) { if (tag[i] == Mwildcard || Tags[i] == Mwildcard) return true; if (tag[i] != Tags[i]) return false; } return true; } public bool HasWildcard { get { for (int i = 0; i < 4; i++) if (Tags[i] == Mwildcard) return true; return false; } } } public delegate object Loader (Tag tag, object extra_info); internal class MDatabaseInfo { // These come from the declartion plist of the database. internal string Description; internal string Filename; internal FileInfo Validater; internal int Version; internal MSymbol Format; internal MSymbol Schema; internal string SchemaFile; internal MPlist Props; public MDatabaseInfo () { Format = Schema = MSymbol.nil; } private static int parse_version (MPlist plist) { string[] str; int major, minor, release; if (! plist.IsMText) return 0xFFFFFF; str = plist.Text.ToString ().Split ('.'); if (str.Length != 3) return 0xFFFFFF; try { major = int.Parse (str[0]); } catch { return 0xFFFFFF; } try { minor = int.Parse (str[1]); } catch { return 0xFFFFFF; } try { release = int.Parse (str[2]); } catch { return 0xFFFFFF; } return ((major << 16) | (minor << 8) | release); } public MDatabaseInfo (MPlist plist) { Format = MSymbol.plist; if (plist.IsMText) { Filename = plist.Text.ToString (); plist = plist.Next; } else if (plist.IsPlist) { MPlist p = plist.Plist; if (p.IsMText) Filename = p.Text.ToString (); p = p.Next; if (! p.IsEmpty) { if (p.IsSymbol) Format = p.Symbol; p = p.Next; if (! p.IsEmpty) { if (p.IsSymbol) Schema = p.Symbol; p = p.Next; if (p.IsMText) SchemaFile = p.Text.ToString (); } } plist = plist.Next; } Version = 0; Props = new MPlist (); foreach (MPlist pl in plist) { if (pl.IsPlist) { MPlist p = pl.Plist; if (p.IsSymbol && p.Symbol == Mversion) Version = parse_version (p.Next); else Props.Put (pl.Key, pl.Val); } else if (pl.IsSymbol) { MPlist p = new MPlist (); p.Add (MSymbol.symbol, pl.Symbol); p.Add (MSymbol.symbol, MSymbol.t); Props.Put (MSymbol.plist, p); } } } public void Merge (MDatabaseInfo src) { if (Validater == null) Validater = src.Validater; if (Version == 0) Version = src.Version; if (Format == MSymbol.nil) Format = src.Format; if (Schema == MSymbol.nil) Schema = src.Schema; if (SchemaFile == null) SchemaFile = src.SchemaFile; foreach (MPlist p in src.Props) if (Props.Assq (p.Plist.Symbol) == null) Props.Push (p.Key, p.Val); } public override string ToString () { string str = ("#"; } } // Dictionaries for normal databases. private static Dictionary> ndict = new Dictionary> (); // Dictionaries for databases of DBType WILDCARD private static Dictionary> wdict = new Dictionary> (); private static MDatabaseDir[] DBDirs = new MDatabaseDir[4]; private static DateTime LastUpdateTime = new DateTime (0); private static readonly MSymbol Mversion = MSymbol.Of ("version"); private static readonly MSymbol Mwildcard = MSymbol.Of ("*"); private static readonly MSymbol Mchar_table = MSymbol.Of ("char-table"); private static readonly MSymbol Mcharset = MSymbol.Of ("charset"); /// Type of database private enum MDBType { /// The database was defined automatically from one of mdb.dir /// files with no wildcard tag. AUTO, /// The database was defined automatically from one of mdb.dir /// files with a wildcard tag to define multiple databases /// of the same kind. MULTIPLE, /// The database was defined explicitely by MDatabase.Define /// to use the normal loader. EXPLICIT, /// The database was defined explicitely by MDatabase.Define /// to use a special loader. UNKNOWN, /// The database is for defining multiple databases of the /// same kind with a wildcard tag. WILDCARD, }; /// Status of database private enum MDBStatus { // The database file has not yet been decided, or is not yet // expanded if DBType is WILDCARD. NOT_READY, // The database file was decided, or has been expanded if // DBType is WILDCARD. READY, // The database is disabled. It means that the database file // is not readable, the version is not supported by the // current system, or the validation was failed. DISABLED, // The database is deleted by the modificaiton of mdb.dir, or // is overwritten by a new explicit definition.. INVALID, }; public Tag tag; private Loader loader; private object ExtraInfo; // Directory of the database file. // -1:unknown, 0:absolute, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3] private int DirIndex; // Directory of the mdb.dir defining the database file. // 0: EXPLICIT or UNKNOWN, 1:DBDirs[1], 2:DBDirs[2], 3:DBDirs[3] private int ListIndex; private MDBType DBType; private MDBStatus DBStatus; internal MDatabaseInfo Info; // File in which the database contents is stored. internal FileInfo FileInfo; // When the database file is checked (or validated). internal DateTime CheckedTime; static MDatabase () { string share_dir = (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData)); string usr_dir = (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData)); try { string dir = Environment.GetEnvironmentVariable ("M17NDIR"); DBDirs[1] = new MDatabaseDir (dir); } catch { try { DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d")); } catch (ArgumentException) { DBDirs[1] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d")); } } DBDirs[2] = new MDatabaseDir (null); DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n")); update_all (); } public static string ApplicationDir { get { return (DBDirs[1].Dirname); } set { DBDirs[2] = new MDatabaseDir (value); update_all (); } } private static bool update_all () { bool updated = false; for (int i = 1; i < 4; i++) if (DBDirs[i].Dirname != null) { DBDirs[i].Refresh (); if (LastUpdateTime < DBDirs[i].ListChangeTime) { update_list (i); updated = true; } if (LastUpdateTime < DBDirs[i].DirChangeTime) { update_dir (i); updated = true; } } LastUpdateTime = DateTime.Now; return updated; } public static void Dump () { update_all (); Console.WriteLine ("[DBDirs]"); for (int i = 1; i < 4; i++) if (DBDirs[i].Dirname != null) { if (DBDirs[i].DirInfo != null) { Console.Write ("{0}:{1}", i, DBDirs[i].DirInfo.FullName); if (DBDirs[i].ListInfo != null) Console.WriteLine (" {0}", DBDirs[i].ListInfo); else Console.WriteLine (" .. no mdb.dir"); } else Console.WriteLine ("{0}:{1} .. not exist", i, DBDirs[i].Dirname); } Console.WriteLine ("[WDICT]"); foreach (KeyValuePair> kv in wdict) foreach (MDatabase mdb in kv.Value) Console.WriteLine (mdb); Console.WriteLine ("[NDICT]"); foreach (KeyValuePair> kv in ndict) foreach (MDatabase mdb in kv.Value) Console.WriteLine (mdb); } private static void register (MDatabase mdb) { Dictionary> dict = mdb.DBType == MDBType.WILDCARD ? wdict : ndict; List mdbs; if (dict.TryGetValue (mdb.tag, out mdbs)) { for (int i = 0; i < mdbs.Count; i++) if (mdbs[i].ListIndex == mdb.ListIndex) { mdbs[i].DBStatus = MDBStatus.INVALID; mdbs[i] = mdb; return; } mdbs.Add (mdb); } else { mdbs = new List (1); mdbs.Add (mdb); dict.Add (mdb.tag, mdbs); } } private static void register (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info) { Dictionary> dict = tag.HasWildcard ? wdict : ndict; List mdbs; MDatabase mdb; if (dict.TryGetValue (tag, out mdbs)) for (int i = 0; i < mdbs.Count; i++) { mdb = mdbs[i]; if (mdb.ListIndex == list_idx && mdb.DirIndex >= dir_idx) { if (mdb.DBStatus == MDBStatus.INVALID) M17n.DebugPrint ("registering: {0}\n", mdb); else M17n.DebugPrint ("updating: {0}\n", mdb); mdb.DirIndex = dir_idx; if (dict == wdict) { mdb.DBType = MDBType.WILDCARD; mdb.DBStatus = MDBStatus.NOT_READY; } else if (dir_idx == -1) { mdb.DBType = MDBType.AUTO; mdb.DBStatus = MDBStatus.NOT_READY; } else { mdb.DBType = MDBType.MULTIPLE; mdb.DBStatus = MDBStatus.READY; } mdb.Info = info; return; } } else { mdbs = new List (1); dict.Add (tag, mdbs); } mdb = new MDatabase (list_idx, dir_idx, tag, info); M17n.DebugPrint ("registering: {0}\n", mdb); mdbs.Add (mdb); } public MDatabase (Tag tag, Loader loader, object extra_info) { this.tag = tag; this.loader = loader; DBType = MDBType.UNKNOWN; DBStatus = MDBStatus.READY; DirIndex = 0; ListIndex = 0; ExtraInfo = extra_info; register (this); } public MDatabase (Tag tag, string filename) { this.tag = tag; DBType = MDBType.EXPLICIT; if (Path.IsPathRooted (filename)) { DirIndex = 0; if (File.Exists (filename)) DBStatus = MDBStatus.READY; else DBStatus = MDBStatus.INVALID; } else { DirIndex = -1; DBStatus = MDBStatus.NOT_READY; } ListIndex = 0; Info = new MDatabaseInfo (); Info.Filename = filename; register (this); } private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info) { this.tag = tag; this.Info = info; DBType = this.tag.HasWildcard ? MDBType.WILDCARD : MDBType.AUTO; if (tag[0] == Mchar_table || tag[0] == Mcharset) Info.Format = tag[0]; ListIndex = list_idx; DirIndex = dir_idx; if (Path.IsPathRooted (Info.Filename)) DBStatus = MDBStatus.READY; else DBStatus = MDBStatus.NOT_READY; if (Info.Format == Mchar_table) MCharProp.Define (tag[2], this); } public override String ToString () { string str = ("#"; } // Update (or disable) databases defined by "mdb.dir" in // DBDirs[list_idx]. private static void update_list (int list_idx) { M17n.DebugPrint ("Updating list: {0}\n", list_idx); // At first disable all target databases. foreach (KeyValuePair> kv in wdict) foreach (MDatabase mdb in kv.Value) if (mdb.ListIndex == list_idx) { M17n.DebugPrint ("deleting: {0}\n", mdb); mdb.DBStatus = MDBStatus.INVALID; break; } foreach (KeyValuePair> kv in ndict) foreach (MDatabase mdb in kv.Value) if (mdb.ListIndex == list_idx) { M17n.DebugPrint ("deleting: {0}\n", mdb); mdb.DBStatus = MDBStatus.INVALID; break; } FileInfo dblist = DBDirs[list_idx].ListInfo; if (dblist == null) return; MPlist plist = null; using (FileStream stream = File.OpenRead (dblist.FullName)) { plist = new MPlist (stream); } if (plist == null) return; foreach (MPlist pl in plist) if (pl.IsPlist) { try { MPlist p = pl.Plist; Tag tag = new Tag (ref p); MDatabaseInfo info = new MDatabaseInfo (p); int dir_idx = Path.IsPathRooted (info.Filename) ? 0 : -1; register (list_idx, dir_idx, tag, info); } catch (Exception e) { Console.WriteLine (e.Message + ": " + pl.Plist); } } } // Update (or disable) databases in DBDirs[dir_idx]. private static void update_dir (int dir_idx) { M17n.DebugPrint ("Updating dir: {0}\n", dir_idx); // Reset all databases in DBDirs[dir_idx]. foreach (KeyValuePair> kv in ndict) foreach (MDatabase mdb in kv.Value) if (mdb.DirIndex >= dir_idx) { M17n.DebugPrint ("disabling: {0}\n", mdb); mdb.DBStatus = MDBStatus.NOT_READY; mdb.DirIndex = -1; } // Re-expand all WILDCARD databases in DBDirs[dir_idx]. if (DBDirs[dir_idx].DirInfo != null) foreach (KeyValuePair> kv in wdict) foreach (MDatabase mdb in kv.Value) if (mdb.DBStatus == MDBStatus.READY) { M17n.DebugPrint ("re-expanding: {0}\n", mdb); register_files (DBDirs[dir_idx].Dirname, mdb.ListIndex, dir_idx, mdb.Info); } } private static void register_files (string dir, int list_idx, int dir_idx, MDatabaseInfo base_info) { List files = new List (); MGlob.FileList (ref files, dir, base_info.Filename); foreach (FileInfo fileinfo in files) { MPlist plist = null; using (FileStream stream = fileinfo.OpenRead ()) { plist = new MPlist (stream, 1); } if (plist != null && plist.IsPlist) { plist = plist.Plist; Tag tag = new Tag (ref plist); if (! tag.HasWildcard && tag.Match (tag)) { MDatabaseInfo info = new MDatabaseInfo (plist); info.Merge (base_info); if (Path.IsPathRooted (base_info.Filename)) info.Filename = fileinfo.FullName; else info.Filename = fileinfo.Name; register (list_idx, dir_idx, tag, info); } } } } private void expand_wildcard () { M17n.DebugPrint ("expanding: {0}\n", this); if (DirIndex == 0) register_files (null, ListIndex, DirIndex, Info); else for (int i = 1; i < 4; i++) if (DBDirs[i].DirInfo != null) register_files (DBDirs[i].DirInfo.FullName, ListIndex, i, Info); DBStatus = MDBStatus.READY; } private static void maybe_expand_wildcard (Tag tag) { foreach (KeyValuePair> kv in wdict) { M17n.DebugPrint ("expand check: {0}\n", kv.Key); if (kv.Key.Match (tag)) { foreach (MDatabase mdb in kv.Value) { if (mdb.DBStatus == MDBStatus.NOT_READY) mdb.expand_wildcard (); } } } } private bool update_status () { if (DBType == MDBType.UNKNOWN) return true; if (DBStatus == MDBStatus.INVALID) return false; if (DBStatus == MDBStatus.READY) { if (DirIndex > 0 || File.Exists (FileInfo.FullName)) return true; DBStatus = MDBStatus.INVALID; return false; } for (int i = 1; i < 4; i++) if (DBDirs[i] != null && DBDirs[i].Dirname != null) { string filename = Path.Combine (DBDirs[i].Dirname, Info.Filename); if (File.Exists (filename)) { FileInfo = new FileInfo (filename); DirIndex = i; return true; } } return false; } public static MDatabase Find (Tag tag) { List mdbs; MDatabase mdb = null; if (tag.HasWildcard) throw new ArgumentException ("Wildcard not allowed: " + tag); if (ndict.TryGetValue (tag, out mdbs)) { mdbs.Sort (); for (int i = 0; i < mdbs.Count; i++) { mdb = mdbs[i]; if (mdb.ListIndex == 0) return mdb; if (mdb.DBStatus == MDBStatus.READY) break; } } if (! update_all () && mdb != null) return mdb; maybe_expand_wildcard (tag); if (! ndict.TryGetValue (tag, out mdbs)) return null; for (int i = 0; i < mdbs.Count; i++) if ((mdb = mdbs[i]).update_status ()) return mdb; return null; } public static List List (Tag tag) { List list = new List (); update_all (); maybe_expand_wildcard (tag); if (tag.HasWildcard) { foreach (KeyValuePair> kv in ndict) if (kv.Key.Match (tag)) foreach (MDatabase mdb in kv.Value) if (mdb.update_status ()) { list.Add (mdb); break; } } else { List mdbs; if (ndict.TryGetValue (tag, out mdbs)) foreach (MDatabase mdb in mdbs) if (mdb.update_status ()) { list.Add (mdb); break; } } return list; } public object Load () { if (loader != null) return loader (tag, ExtraInfo); if (Info.Format == Mchar_table) throw new Exception ("Use Load (MCharTable) to load this database"); if (Info.Format == Mcharset) throw new Exception ("Use Load (MCharset) to load this database"); if (! update_status ()) throw new Exception ("Database invalid"); MPlist plist = null; using (FileStream stream = File.OpenRead (FileInfo.FullName)) plist = new MPlist (stream); return plist; } public object Load (MSymbol key, MSymbol stop) { if (loader != null || Info.Format != MSymbol.plist) throw new ArgumentException ("Key can't be specified for loading this database"); if (! update_status ()) throw new Exception ("Database invalid"); MPlist plist = null; using (FileStream stream = File.OpenRead (FileInfo.FullName)) plist = new MPlist (stream, key, stop); return plist; } /// Return a list of currently available database /// directory names. public static string[] DirectoryList () { List dirs = new List (); for (int i = 1; i < 4; i++) if (DBDirs[i].Dirname != null) dirs.Add (DBDirs[i].Dirname); return dirs.ToArray (); } // For IComparable public int CompareTo (MDatabase other) { return (ListIndex == other.ListIndex ? DirIndex - other.DirIndex : ListIndex - other.ListIndex); } } }