X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=MDatabase.cs;h=01d575520511262f884c80a0e13bd34059e61462;hb=eefa0b9efaf141df3fbfadb8bc31b09e6d55adc3;hp=5557257f8d094c7f8317f377116b2dbaa2cbc6e7;hpb=fd26e0940f7ab83cfdda0d244bbb1d1bcef52f4b;p=m17n%2Fm17n-lib-cs.git diff --git a/MDatabase.cs b/MDatabase.cs index 5557257..01d5755 100644 --- a/MDatabase.cs +++ b/MDatabase.cs @@ -1,152 +1,390 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; +using System.Xml; + using M17N; using M17N.Core; namespace M17N.Core { - public class MDatabase + internal class MGlob { - public struct Tag - { - public MSymbol Tag0, Tag1, Tag2, Tag3; + static readonly char sep = Path.DirectorySeparatorChar; - public Tag (MSymbol tag0) - { - Tag0 = tag0; Tag1 = Tag2 = Tag3 = MSymbol.nil; - } + public static void FileList (ref List files, + string dir, string pattern) + { + int len = pattern.Length; - public Tag (MSymbol tag0, MSymbol tag1) + if (Path.IsPathRooted (pattern)) { - Tag0 = tag0; Tag1 = tag1; Tag2 = Tag3 = MSymbol.nil; - } + int headsep = 0; + int i; - public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2) - { - Tag0 = tag0; Tag1 = tag1; Tag2 = tag2; Tag3 = MSymbol.nil; + 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); } - - public Tag (MSymbol tag0, MSymbol tag1, MSymbol tag2, MSymbol tag3) + else { - Tag0 = tag0; Tag1 = tag1; Tag2 = tag2; Tag3 = tag3; + if (dir == null) + dir = Directory.GetCurrentDirectory (); } + if (Directory.Exists (dir)) + list (ref files, new DirectoryInfo (dir), pattern); } - public delegate object Loader (Tag tag, object extra_info); - - private class MDatabaseDir + private static void list (ref List files, + DirectoryInfo dirinfo, string pattern) { - private const string ListFileName = "mdb.dir"; - - public string Dirname; - public DirectoryInfo DirInfo; - public DateTime LastScanned; - public FileInfo ListInfo; - - private static void GetInfo (string dirname, out DirectoryInfo dirinfo, - out FileInfo listinfo) - { - if (Directory.Exists (dirname)) - try { dirinfo = new DirectoryInfo (dirname); - try { listinfo = dirinfo.GetFiles (ListFileName)[0]; - } catch { listinfo = null; } - } catch { dirinfo = null; listinfo = null; } + int len = pattern.Length; + int i; + + M17n.DebugPrint ("Listing {0} in {1} ...", pattern, dirinfo); + for (i = 0; i < len && pattern[i] != sep; i++); + try { + if (i == len) + { + FileInfo[] listing = dirinfo.GetFiles (pattern); + i = listing.Length; + foreach (FileInfo elt in listing) + files.Add (elt); + } else { - dirinfo = null; - listinfo = null; + string tail = pattern.Substring (i + 1); + pattern = pattern.Substring (0, i); + DirectoryInfo[] listing = dirinfo.GetDirectories (pattern); + + i = listing.Length; + foreach (DirectoryInfo elt in listing) + list (ref files, elt, tail); } + } catch { } + M17n.DebugPrint (" found {0} files\n", i); + } + } - public MDatabaseDir (string dirname) - { - Dirname = dirname; - GetInfo (dirname, out DirInfo, out ListInfo); - } + internal class MDatabaseDir + { + private const string ListFileName = "mdb.dir"; - public bool StatusChanged { - get { - bool exists = Directory.Exists (Dirname); + public string Dirname; + public DirectoryInfo DirInfo; + public DateTime DirChangeTime; + public FileInfo ListInfo; + public DateTime ListChangeTime; - if (DirInfo != null) + public MDatabaseDir (string dirname) + { + Dirname = dirname; + DirChangeTime = ListChangeTime = DateTime.Now; + } + + public void Refresh () + { + if (DirInfo != null) + { + if (Dirname != null && Directory.Exists (Dirname)) { - if (! exists) - { - DirInfo = null; - ListInfo = null; - LastScanned = new DateTime (0); - } - if (LastScanned.Year == 0) - return true; DirInfo.Refresh (); - if (ListInfo != null) - ListInfo.Refresh (); - return (LastScanned < DirInfo.LastWriteTime - || LastScanned < ListInfo.LastWriteTime); + if (DirChangeTime < DirInfo.LastWriteTime) + DirChangeTime = DirInfo.LastWriteTime; } else { - if (exists) + 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) { - DirInfo = new DirectoryInfo (Dirname); - try { - ListInfo = DirInfo.GetFiles (ListFileName)[0]; - } catch { - ListInfo = null; - } - return true; + Tags[i] = plist.Symbol; + plist = plist.Next; } - return false; + 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 void UpdateStatus () + public override int GetHashCode () { - if (DirInfo != null) - LastScanned = DateTime.UtcNow; + return (Tags[0].GetHashCode () ^ Tags[1].GetHashCode () + ^ Tags[2].GetHashCode () ^ Tags[3].GetHashCode ()); } - public FileInfo[] Scan (string filename) + public override string ToString () { - if (DirInfo == null) - return null; - DirInfo.Refresh (); - return DirInfo.GetFiles (filename); + 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; + } } } - internal class MDatabaseInfo { - internal DirectoryInfo Dir; + public delegate MPlist Loader (Tag tag, object extra_info); + + internal class MDatabaseInfo + { + // These come from the declartion plist of the database. internal string Description; - internal MText Filename; - internal FileInfo FileInfo; + internal string Filename; internal FileInfo Validater; internal int Version; internal MSymbol Format; internal MSymbol Schema; - internal MText SchemaFile; - internal DateTime ModifiedTime; + 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.nil; + if (plist.IsMText) + { + Format = MSymbol.plist; + 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.IsSymbol) + { + Format = p.Symbol; + p = p.Next; + } + 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 = ("#"; + } } - private static Dictionary DBDict - = new Dictionary (); + // 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[3]; + private static MDatabaseDir[] DBDirs = new MDatabaseDir[4]; - private const string SystemDirectory = "/usr/share/m17n"; - private static readonly MSymbol Mversion = MSymbol.Of ("version"); + private static DateTime LastUpdateTime = new DateTime (0); + + private static readonly MSymbol Mversion = "version"; + private static readonly MSymbol Mwildcard = "*"; + private static readonly MSymbol Mchar_table = "char-table"; + private static readonly MSymbol Mcharset = "charset"; + private static readonly MSymbol Mxml = "xml"; + + private static TimeSpan CheckInterval = new TimeSpan (50000000); /// Type of database private enum MDBType { - /// The database was defined automatically from mdb.dir - /// file(s) with no wildcard tag. + /// The database was defined automatically from one of mdb.dir + /// files with no wildcard tag. AUTO, - /// The database was defined automatically from mdb.dir - /// file(s) with a wildcard tag to define multiple databases + /// 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 @@ -155,35 +393,57 @@ namespace M17N.Core /// 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 is currently disabled. It means that the - // database file is not readable or the database is deleted by - // the modification of "mdb.dir". + // 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 file has not yet been loaded, or was modified - // after the previous loading. - OUTDATED, - // The database file has not been modified after the previous - // loading. - UPDATED, - // The database file is updated but the validation was failed - // or the version is not supported by the current system. + // The database is deleted by the modificaiton of mdb.dir, or + // is overwritten by a new explicit definition. INVALID, }; - public readonly Tag tag; + 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 DateTime LoadedTime; internal MDatabaseInfo Info; + // File in which the database contents is stored. This is null + // when DBStatus is NOT_READY. + internal FileInfo FileInfo; + // When the database file is loaded last. + internal DateTime LastLoaded = DateTime.Now; - public static string ApplicationDirectory; + public enum LoadStatus + { + None, + InvalidLoadMethod, + NotAvailable, + NotReadable, + InvalidContents, + }; + + public LoadStatus LastLoadStatus = LoadStatus.None; static MDatabase () { @@ -193,171 +453,780 @@ namespace M17N.Core (Environment.SpecialFolder.ApplicationData)); try { - DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d")); - } catch (ArgumentException) { - DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n_d")); + 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[1] = null; - DBDirs[2] = new MDatabaseDir (Path.Combine (share_dir, "m17n")); + DBDirs[2] = new MDatabaseDir (null); + DBDirs[3] = new MDatabaseDir (Path.Combine (share_dir, "m17n")); + update_all (true); + } + + public static string ApplicationDir + { + get { return (DBDirs[1].Dirname); } + set { DBDirs[2] = new MDatabaseDir (value); update_all (true); } + } + + // Update all listing and directories. Return true iff some are + // really updated. + private static bool update_all (bool force) + { + if (! force && DateTime.Now - LastUpdateTime < CheckInterval) + return false; + + 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; + } + } + if (updated) + LastUpdateTime = DateTime.Now; + return updated; } - private MDatabase (Tag tag, Loader loader, object extra_info) + public static void Dump () + { + update_all (false); + 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) + { + 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; + if (mdb.DBStatus == MDBStatus.INVALID) + M17n.DebugPrint ("registering: {0}\n", mdb); + else + M17n.DebugPrint ("updating: {0}\n", mdb); + 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); } - private MDatabase (Tag tag, string filename) + 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 = new MText (filename); + Info.Filename = filename; + register (this); } - private MDatabase (MPlist plist) + private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info) { - MSymbol[] tags = new MSymbol[4]; - int i; + 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, dir_idx, mdb); + } + } + + private static bool parse_plist_header (FileInfo fileinfo, MDatabase mdb, + out Tag tag, out MDatabaseInfo info) + { + MPlist plist = null; - for (i = 0; plist.IsSymbol; i++, plist = plist.Next) - tags[i] = plist.Symbol; - while (i < 4) - tags[i++] = MSymbol.nil; - tag = new Tag (tags[0], tags[1], tags[2], tags[3]); - if (plist.IsMText) + tag = new Tag (MSymbol.nil); + info = null; + using (FileStream stream = fileinfo.OpenRead ()) { - Info.Filename = plist.Text; - plist = plist.Next; + try { plist = new MPlist (stream, 1); } catch { } } - else if (plist.IsPlist) + if (plist == null || ! plist.IsPlist) + return false; + plist = plist.Plist; + tag = new Tag (ref plist); + if (tag.HasWildcard || ! tag.Match (mdb.tag)) + return false; + info = new MDatabaseInfo (plist); + return true; + } + + private static bool parse_xml_header (FileInfo fileinfo, MDatabase mdb, + out Tag tag, out MDatabaseInfo info) + { + tag = new Tag (MSymbol.nil); + info = null; + using (FileStream stream = fileinfo.OpenRead ()) { - MPlist p = plist.Plist; + try { + MPlist plist = new MPlist (); + XmlTextReader reader = new XmlTextReader (stream); + + reader.WhitespaceHandling = WhitespaceHandling.None; + do { + reader.Read (); + } while (reader.NodeType != XmlNodeType.Element); + plist.Add (MSymbol.symbol, (MSymbol) reader.Name); + reader.Read (); + if (reader.NodeType == XmlNodeType.Element && reader.Name == "tags") + { + reader.Read (); + while (reader.NodeType == XmlNodeType.Element) + { + reader.Read (); + plist.Add (MSymbol.symbol, (MSymbol) reader.Value); + reader.Read (); + reader.Read (); + } + tag = new Tag (ref plist); + if (tag.HasWildcard || ! tag.Match (mdb.tag)) + return false; + info = new MDatabaseInfo (plist); + return true; + } + } catch (Exception e) { + Console.WriteLine ("error {0}", e); + } + } + return false; + } - if (p.IsMText) - Info.Filename = plist.Text; - p = p.Next; - if (! p.IsEmpty) + private static void register_files (string dir, int dir_idx, MDatabase mdb) + { + int list_idx = mdb.ListIndex; + List files = new List (); + MGlob.FileList (ref files, dir, mdb.Info.Filename); + foreach (FileInfo fileinfo in files) + { + Tag tag; + MDatabaseInfo info; + + if (mdb.Info.Format == MSymbol.plist + ? parse_plist_header (fileinfo, mdb, out tag, out info) + : parse_xml_header (fileinfo, mdb, out tag, out info)) { - if (p.IsSymbol) - Info.Format = p.Symbol; - p = p.Next; - if (! p.IsEmpty) - { - if (p.IsSymbol) - Info.Schema = p.Symbol; - p = p.Next; - if (p.IsMText) - Info.SchemaFile = p.Text; - } + info.Merge (mdb.Info); + if (Path.IsPathRooted (mdb.Info.Filename)) + info.Filename = fileinfo.FullName; + else + info.Filename = fileinfo.Name; + register (list_idx, dir_idx, tag, info); } - plist = plist.Next; } - DBStatus = MDBStatus.OUTDATED;; - Info.Version = 0; - Info.Props = new MPlist (); - foreach (MPlist pl in plist) + } + + private void expand_wildcard () + { + M17n.DebugPrint ("expanding: {0}\n", this); + + if (DirIndex == 0) + register_files (null, DirIndex, this); + else + for (int i = 1; i < 4; i++) + if (DBDirs[i].DirInfo != null) + register_files (DBDirs[i].DirInfo.FullName, i, this); + DBStatus = MDBStatus.READY; + } + + private static void maybe_expand_wildcard (Tag tag) + { + foreach (KeyValuePair> kv in wdict) { - if (pl.IsPlist) + M17n.DebugPrint ("expand check: {0}\n", kv.Key); + if (kv.Key.Match (tag)) { - MPlist p = pl.Plist; - - if (p.IsSymbol && p.Symbol == Mversion) + foreach (MDatabase mdb in kv.Value) { - Info.Version = parse_version (p.Next); - if (M17n.Version < Info.Version) - DBStatus = MDBStatus.DISABLED; + if (mdb.DBStatus == MDBStatus.NOT_READY) + mdb.expand_wildcard (); } } - Info.Props.Put (pl.Key, pl.Val); } } - private static int parse_version (MPlist plist) - { - string[] str; - int major, minor, release; + // Update the status. Return true iff the database file is + // readable but changed. - 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); + private bool update_status () + { + if (DBType == MDBType.UNKNOWN) + return true; + update_all (false); + if (DBStatus == MDBStatus.INVALID) + return false; + if (DBStatus != MDBStatus.NOT_READY) + { + try { + FileInfo.Refresh (); + } catch { + DBStatus = MDBStatus.INVALID; + return false; + } + if (LastLoaded >= FileInfo.LastWriteTime) + return false; + DBStatus = MDBStatus.READY; + return true; + } + 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; + DBStatus = MDBStatus.READY; + return true; + } + } + return false; } - public static MDatabase Define (Tag tag, Loader loader, object extra_info) + public static MDatabase Find (Tag tag) { - MDatabase db = MDatabase.Find (tag); + List mdbs; + MDatabase mdb = null; + + if (tag.HasWildcard) + throw new ArgumentException ("Wildcard not allowed: " + tag); - if (db != null) + 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 (false) && mdb != null) + return mdb; + maybe_expand_wildcard (tag); + if (! ndict.TryGetValue (tag, out mdbs)) + return null; + for (int i = 0; i < mdbs.Count; i++) { - db.loader = loader; - db.ExtraInfo = extra_info; - db.DBType = MDBType.EXPLICIT; - db.DBStatus = MDBStatus.OUTDATED; - db.Info = null; - return db; + mdb = mdbs[i]; + mdb.update_status (); + if (mdb.DBStatus == MDBStatus.READY) + return mdb; } - return new MDatabase (tag, loader, extra_info); + return null; } - public static MDatabase Define (Tag tag, string filename) + public static List List (Tag tag) { - MDatabase db = MDatabase.Find (tag); + List list = new List (); - if (db != null) - { - db.loader = null; - db.DBType = MDBType.EXPLICIT; - db.DBStatus = MDBStatus.OUTDATED; - db.Info = new MDatabaseInfo (); - db.Info.Filename = new MText (filename); + update_all (false); + maybe_expand_wildcard (tag); - return db; + if (tag.HasWildcard) + { + foreach (KeyValuePair> kv in ndict) + if (kv.Key.Match (tag)) + foreach (MDatabase mdb in kv.Value) + { + mdb.update_status (); + if (mdb.DBStatus == MDBStatus.READY) + { + list.Add (mdb); + break; + } + } } - return new MDatabase (tag, filename); + else + { + List mdbs; + if (ndict.TryGetValue (tag, out mdbs)) + foreach (MDatabase mdb in mdbs) + { + mdb.update_status (); + if (mdb.DBStatus == MDBStatus.READY) + { + list.Add (mdb); + break; + } + } + } + return list; } - private void update () + private FileStream get_stream () { - for (int i = 0; i < 3; i++) + if (DBStatus != MDBStatus.READY) { - if (DBDirs[0].StatusChanged) - break; + LastLoadStatus = LoadStatus.NotAvailable; + return null; } + + FileStream stream = null; + try { + stream = FileInfo.OpenRead (); + } catch { + LastLoadStatus = LoadStatus.NotReadable; + } + return stream; } - public static MDatabase Find (Tag tag) + public MPlist Load () { - MDatabase db; + if (loader != null) + return loader (tag, ExtraInfo); + if (Info.Format != MSymbol.plist) + throw new Exception ("Not a plist database"); - return (DBDict.TryGetValue (tag, out db) ? db : null); + FileStream stream = get_stream (); + if (stream == null) + return null; + MPlist plist = null; + try { + plist = new MPlist (stream); + LastLoaded = DateTime.Now; + } catch { + LastLoadStatus = LoadStatus.InvalidContents; + } finally { + stream.Dispose (); + } + return plist; } - public object Load () + public MPlist Load (MSymbol key, MSymbol stop) { - return (loader != null ? loader (tag, ExtraInfo) - : load (MSymbol.nil, MSymbol.nil)); + if (loader != null) + throw new Exception ("Partial load is impossible"); + if (Info.Format != MSymbol.plist) + throw new Exception ("Not a plist database"); + + FileStream stream = get_stream (); + if (stream == null) + return null; + try { + MPlist plist = new MPlist (stream, key, stop); + LastLoaded = DateTime.Now; + return plist; + } catch { + LastLoadStatus = LoadStatus.InvalidContents; + return null; + } finally { + stream.Dispose (); + } } - public object Load (MSymbol key, MSymbol stop) + public MPlist Load (MSymbol stop) { if (loader != null) + throw new Exception ("Partial load is impossible"); + if (Info.Format != MSymbol.plist) + throw new Exception ("Not a plist database"); + + FileStream stream = get_stream (); + if (stream == null) + return null; + try { + MPlist plist = new MPlist (stream, stop); + LastLoaded = DateTime.Now; + return plist; + } catch (Exception e) { + Console.WriteLine (e); + LastLoadStatus = LoadStatus.InvalidContents; return null; - return load (key, stop); + } finally { + stream.Dispose (); + } } - private object load (MSymbol key, MSymbol stop) + private XmlTextReader get_reader (XmlDocument doc) { - LoadedTime = DateTime.UtcNow; + XmlTextReader reader; - + try { + if (doc.NameTable != null) + reader = new XmlTextReader (FileInfo.FullName, doc.NameTable); + else + reader = new XmlTextReader (FileInfo.FullName); + reader.WhitespaceHandling = WhitespaceHandling.None; + } catch { + LastLoadStatus = LoadStatus.NotReadable; + reader = null; + } + return reader; + } - return null; + public bool Load (XmlDocument doc) + { + if (Info.Format != Mxml) + throw new Exception ("Not an XML database"); + XmlTextReader reader = get_reader (doc); + if (reader == null) + return false; + try { + doc.Load (reader); + LastLoaded = DateTime.Now; + return true; + } catch (Exception e) { + Console.WriteLine (e); + LastLoadStatus = LoadStatus.InvalidContents; + return false; + } finally { + reader.Close (); + } + } + + public bool Load (XmlDocument doc, MSymbol key, MSymbol stop) + { + if (Info.Format != Mxml) + throw new Exception ("Not an XML database"); + XmlTextReader reader = get_reader (doc); + if (reader == null) + return false; + try { + reader.Read (); + while (reader.NodeType != XmlNodeType.Element) + reader.Read (); + doc.LoadXml ("<" + reader.Name + ">"); + reader.Read (); + XmlNode node = doc.DocumentElement; + while (reader.NodeType == XmlNodeType.Element + ? reader.Name != stop.Name + : reader.NodeType != XmlNodeType.EndElement) + if (reader.NodeType == XmlNodeType.Element + && reader.Name == key.Name) + node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader), + node); + return true; + } catch (Exception e) { + Console.WriteLine (e); + return false; + } finally { + reader.Close (); + } + } + + public bool Load (XmlDocument doc, MSymbol stop) + { + if (Info.Format != Mxml) + throw new Exception ("Not an XML database"); + XmlTextReader reader = get_reader (doc); + if (reader == null) + return false; + try { + reader.Read (); + while (reader.NodeType != XmlNodeType.Element) + reader.Read (); + doc.LoadXml ("<" + reader.Name + ">"); + reader.Read (); + XmlNode node = doc.DocumentElement; + while (reader.NodeType == XmlNodeType.Element + ? reader.Name != stop.Name + : reader.NodeType != XmlNodeType.EndElement) + node = doc.DocumentElement.InsertAfter (doc.ReadNode (reader), node); + return true; + } catch (Exception e) { + Console.WriteLine (e); + return false; + } finally { + reader.Close (); + } + } + + public XmlNode Load (XmlDocument doc, string id, params string[] nodes) + { + if (Info.Format != Mxml) + throw new Exception ("Not an XML database"); + XmlTextReader reader = get_reader (doc); + if (reader == null) + return null; + try { + int len = nodes.Length; + do { + reader.Read (); + } while (reader.NodeType != XmlNodeType.Element); + if (reader.Name != nodes[0]) + return null; + + string ns = reader.GetAttribute ("xmlns"); + XmlNode top = doc.CreateNode (XmlNodeType.Element, nodes[0], ns); + XmlNode node = top; + int i; + + for (i = 1; i + 1 < len; i++) + { + if (! reader.ReadToDescendant (nodes[i])) + return null; + node = node.InsertAfter (doc.CreateNode (XmlNodeType.Element, + nodes[i], ns), null); + } + if (! reader.ReadToDescendant (nodes[i])) + return null; + XmlNode ref_node = null; + while (reader.NodeType != XmlNodeType.EndElement) + { + if (reader.NodeType == XmlNodeType.Element) + { + if (reader.Name == nodes[i] + && (id == null || id == reader.GetAttribute ("id"))) + ref_node = node.InsertAfter (doc.ReadNode (reader), ref_node); + else + reader.Skip (); + } + else + reader.Read (); + } + return top; + } catch (Exception e) { + Console.WriteLine (e); + return null; + } finally { + reader.Close (); + } + } + + /// 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 (); + } + + public MSymbol Format { get { return Info.Format; } } + + public static bool Changed (DateTime time) + { + update_all (false); + return (time < LastUpdateTime); + } + + public bool NeedReload () + { + return update_status (); + } + + // For IComparable + public int CompareTo (MDatabase other) + { + return (ListIndex == other.ListIndex + ? DirIndex - other.DirIndex + : ListIndex - other.ListIndex); } } -} \ No newline at end of file +}