X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=MDatabase.cs;h=01d575520511262f884c80a0e13bd34059e61462;hb=eefa0b9efaf141df3fbfadb8bc31b09e6d55adc3;hp=5b655684825530bcdc4f4340ac8d42b2008f92aa;hpb=929b8da07042da89de11e113839927c130a77bf3;p=m17n%2Fm17n-lib-cs.git diff --git a/MDatabase.cs b/MDatabase.cs index 5b65568..01d5755 100644 --- a/MDatabase.cs +++ b/MDatabase.cs @@ -1,121 +1,287 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; +using System.Xml; + using M17N; using M17N.Core; namespace M17N.Core { - public delegate object MDatabaseLoader (MDatabaseTag tag, - object extra_info); - - public struct MDatabaseTag + internal class MGlob { - public MSymbol Tag0, Tag1, Tag2, Tag3; + static readonly char sep = Path.DirectorySeparatorChar; - public MDatabaseTag (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 MDatabaseTag (MSymbol tag0, MSymbol tag1) - { - Tag0 = tag0; Tag1 = tag1; Tag2 = Tag3 = MSymbol.nil; - } + if (Path.IsPathRooted (pattern)) + { + int headsep = 0; + int i; - public MDatabaseTag (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); + } + else + { + if (dir == null) + dir = Directory.GetCurrentDirectory (); + } + if (Directory.Exists (dir)) + list (ref files, new DirectoryInfo (dir), pattern); + } - public MDatabaseTag (MSymbol tag0, MSymbol tag1, - MSymbol tag2, MSymbol tag3) - { - Tag0 = tag0; Tag1 = tag1; Tag2 = tag2; Tag3 = tag3; + private static void list (ref List files, + DirectoryInfo dirinfo, string pattern) + { + 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 + { + 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 class MDatabase + internal class MDatabaseDir { - private class MDatabaseDir - { - public string Dirname; - public DirectoryInfo Info; - public DateTime LastScanned; + private const string ListFileName = "mdb.dir"; - public MDatabaseDir (string dirname) - { - Dirname = dirname; - try { - Info = new DirectoryInfo (dirname); - } finally { - Info = null; - } - } + public string Dirname; + public DirectoryInfo DirInfo; + public DateTime DirChangeTime; + public FileInfo ListInfo; + public DateTime ListChangeTime; - public bool StatusChanged { - get { - bool exists = Directory.Exists (Dirname); + public MDatabaseDir (string dirname) + { + Dirname = dirname; + DirChangeTime = ListChangeTime = DateTime.Now; + } - if (Info != null) + public void Refresh () + { + if (DirInfo != null) + { + if (Dirname != null && Directory.Exists (Dirname)) { - if (! exists) - { - Info = null; - return true; - } - Info.Refresh (); - return (LastScanned < Info.LastWriteTime); + DirInfo.Refresh (); + 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) { - Info = new DirectoryInfo (Dirname); - 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 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 void UpdateStatus () + public bool Match (Tag tag) { - LastScanned = DateTime.Now; + 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; + } } } - private class MDatabaseDefinition + public delegate MPlist Loader (Tag tag, object extra_info); + + internal class MDatabaseInfo { - private static readonly MSymbol Mversion; - public MDatabaseTag Tag; - public string Description; - public MText Filename; - public bool IsWildcard; - public MSymbol Format; - public MSymbol Schema; - public MText SchemaFile; - public MPlist props; - public bool Supported; + // 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; - static MDatabaseDefinition () + public MDatabaseInfo () { - Mversion = new MSymbol ("version"); + Format = Schema = MSymbol.nil; } - public MDatabaseDefinition (MPlist plist) + private static int parse_version (MPlist plist) { - MSymbol[] tags = new MSymbol[4]; - int i; + string[] str; + int major, minor, release; - for (i = 0; plist.IsSymbol; i++, plist = plist.Next) - tags[i] = plist.Symbol; - while (i < 4) - tags[i++] = MSymbol.nil; - Tag = new MDatabaseTag (tags[0], tags[1], tags[2], tags[3]); + 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) { - Filename = plist.Text; + Format = MSymbol.plist; + Filename = plist.Text.ToString (); plist = plist.Next; } else if (plist.IsPlist) @@ -123,213 +289,944 @@ namespace M17N.Core MPlist p = plist.Plist; if (p.IsMText) - Filename = plist.Text; - p = p.Next; - if (! p.IsEmpty) { - if (p.IsSymbol) - Format = p.Symbol; + 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.IsEmpty) - { - if (p.IsSymbol) - Schema = p.Symbol; - p = p.Next; - if (p.IsMText) - SchemaFile = p.Text; - } } + if (p.IsMText) + SchemaFile = p.Text.ToString (); plist = plist.Next; } - Supported = true; - props = new MPlist (); + + Version = 0; + Props = new MPlist (); foreach (MPlist pl in plist) { if (pl.IsPlist) { MPlist p = pl.Plist; - if (p.IsSymbol && p.Symbol == Mversion - && ! check_version (p.Next)) - Supported = false; - props.Put (pl); + 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); } } } - private bool check_version (MPlist plist) + public void Merge (MDatabaseInfo src) { - string[] str; - int major, minor, release; + 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); + } - if (! plist.IsMText) - return false; - str = plist.Text.ToString ().Split ('.'); - if (str.Length != 3) - return false; - try { major = int.Parse (str[0]); } catch { return false; } - try { minor = int.Parse (str[1]); } catch { return false; } - try { release = int.Parse (str[2]); } catch { return false; } - return (M17N.MajorVersion > major - || (M17N.MajorVersion == major - && (M17N.MinorVersion > minor - || (M17N.MinorVersion == minor - && M17N.ReleaseNumber >= release)))); + public override string ToString () + { + string str = ("#"; } } - private static MDatabaseDir[] DBDirs = new MDatabaseDir[3]; + // Dictionaries for normal databases. + private static Dictionary> ndict + = new Dictionary> (); - private const string SystemDirectory = "/usr/share/m17n"; + // Dictionaries for databases of DBType WILDCARD + private static Dictionary> wdict + = new Dictionary> (); - private static Dictionary DBDict - = new Dictionary (); + private static MDatabaseDir[] DBDirs = new MDatabaseDir[4]; + + 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. - AUTO_WILDCARD, - /// The database was defined explicitely (by MDatabaseDefine). - EXPLICIT + 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 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. - // If this is for a database directory, the directory is - // readable but "mdb.dir" doesn't exist in it. - INVALID + // The database is deleted by the modificaiton of mdb.dir, or + // is overwritten by a new explicit definition. + INVALID, }; - public readonly MDatabaseTag Tag; - private MDatabaseLoader Loader; + public Tag tag; + private Loader loader; private object ExtraInfo; - - private bool IsSystemDatabase; - private DirectoryInfo Dir; - private MText Filename; - private FileInfo FileInfo; - private FileInfo Validater; - private int MajorVersion, MinorVersion, ReleaseNumber; + // 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; - private MSymbol Format; - private MSymbol Schema; - private DateTime Mtime; - private DateTime Ltime; - private MPlist Props; + 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, + }; - private MDatabase (MDatabaseTag tag, MDatabaseLoader loader, - object extra_info) + public LoadStatus LastLoadStatus = LoadStatus.None; + + 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 (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) { - Tag = tag; - Loader = loader; + 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; + } + + 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); + } + + 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 (MDatabaseTag tag, string filename) + private MDatabase (int list_idx, int dir_idx, Tag tag, MDatabaseInfo info) { - Tag = tag; - Filename = new MText (filename); + 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 = ("#"; } - public static MDatabase Define (MDatabaseTag tag, MDatabaseLoader loader, - object extra_info) + // Update (or disable) databases defined by "mdb.dir" in + // DBDirs[list_idx]. + private static void update_list (int list_idx) { - MDatabase db = MDatabase.Find (tag); + 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; + } - if (db != null) + FileInfo dblist = DBDirs[list_idx].ListInfo; + if (dblist == null) + return; + + MPlist plist = null; + using (FileStream stream = File.OpenRead (dblist.FullName)) { - db.Loader = loader; - db.ExtraInfo = extra_info; - return db; + plist = new MPlist (stream); } - return new MDatabase (tag, loader, extra_info); + 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); + } + } } - public static MDatabase Define (MDatabaseTag tag, string filename) + // Update (or disable) databases in DBDirs[dir_idx]. + private static void update_dir (int dir_idx) { - MDatabase db = MDatabase.Find (tag); + 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); + } + } - if (db != null) + private static bool parse_plist_header (FileInfo fileinfo, MDatabase mdb, + out Tag tag, out MDatabaseInfo info) + { + MPlist plist = null; + + tag = new Tag (MSymbol.nil); + info = null; + using (FileStream stream = fileinfo.OpenRead ()) { - db.Loader = null; - db.Filename = new MText (filename); - return db; + try { plist = new MPlist (stream, 1); } catch { } } - return new MDatabase (tag, filename); + 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; } - static MDatabase () + private static bool parse_xml_header (FileInfo fileinfo, MDatabase mdb, + out Tag tag, out MDatabaseInfo info) { - string share_dir = (Environment.GetFolderPath - (Environment.SpecialFolder.CommonApplicationData)); - string usr_dir = (Environment.GetFolderPath - (Environment.SpecialFolder.ApplicationData)); + tag = new Tag (MSymbol.nil); + info = null; + using (FileStream stream = fileinfo.OpenRead ()) + { + 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; + } + + 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)) + { + 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); + } + } + } + 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) + { + 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 (); + } + } + } + } + + // Update the status. Return true iff the database file is + // readable but changed. + + 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 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 (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++) + { + mdb = mdbs[i]; + mdb.update_status (); + if (mdb.DBStatus == MDBStatus.READY) + return mdb; + } + return null; + } + + public static List List (Tag tag) + { + List list = new List (); + + update_all (false); + maybe_expand_wildcard (tag); + + 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; + } + } + } + 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 FileStream get_stream () + { + if (DBStatus != MDBStatus.READY) + { + LastLoadStatus = LoadStatus.NotAvailable; + return null; + } + + FileStream stream = null; + try { + stream = FileInfo.OpenRead (); + } catch { + LastLoadStatus = LoadStatus.NotReadable; + } + return stream; + } + + public MPlist Load () + { + if (loader != null) + return loader (tag, ExtraInfo); + if (Info.Format != MSymbol.plist) + throw new Exception ("Not a plist database"); + + 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 MPlist Load (MSymbol key, 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 { - DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, ".m17n.d")); - } catch (ArgumentException) { - DBDirs[0] = new MDatabaseDir (Path.Combine (usr_dir, "_m17n.d")); + MPlist plist = new MPlist (stream, key, stop); + LastLoaded = DateTime.Now; + return plist; + } catch { + LastLoadStatus = LoadStatus.InvalidContents; + return null; + } finally { + stream.Dispose (); } - DBDirs[1] = null; - DBDirs[2] = new MDatabaseDir (Path.Combine (share_dir, "m17n")); } - internal static void Update () + 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; + } finally { + stream.Dispose (); + } } + private XmlTextReader get_reader (XmlDocument doc) + { + 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; + } - public static MDatabase Find (MDatabaseTag tag) + public bool Load (XmlDocument doc) { - MDatabase db; + 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 (); + } + } - return (DBDict.TryGetValue (tag, out db) ? db : null); + 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 object Load () + public bool Load (XmlDocument doc, MSymbol stop) { - return (Loader != null ? Loader (Tag, ExtraInfo) - : load (MSymbol.nil, MSymbol.nil)); + 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 object Load (MSymbol key, MSymbol stop) + public XmlNode Load (XmlDocument doc, string id, params string[] nodes) { - if (Loader != null) + if (Info.Format != Mxml) + throw new Exception ("Not an XML database"); + XmlTextReader reader = get_reader (doc); + if (reader == null) return null; - return load (key, stop); + 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 (); + } } - private object load (MSymbol key, MSymbol stop) + /// Return a list of currently available database + /// directory names. + public static string[] DirectoryList () { - Ltime = DateTime.Now; + List dirs = new List (); - + for (int i = 1; i < 4; i++) + if (DBDirs[i].Dirname != null) + dirs.Add (DBDirs[i].Dirname); + return dirs.ToArray (); + } - return null; + 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 (); + } -} \ No newline at end of file + // For IComparable + public int CompareTo (MDatabase other) + { + return (ListIndex == other.ListIndex + ? DirIndex - other.DirIndex + : ListIndex - other.ListIndex); + } + } +}