using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.IO;
using BoxedAppSDK;

namespace Sample7_CustomVirtualDirectory
{
    internal class Consts
    {
        public static int STATFLAG_DEFAULT = 0;
        public static int STATFLAG_NONAME = 1;

        public static int S_FALSE = 1;

        public static int FILE_DIRECTORY_FILE = 1;

        public static uint STATUS_OBJECT_PATH_NOT_FOUND = 0xC000003A;
        public static uint STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034;
        public static uint STATUS_OBJECT_NAME_COLLISION = 0xC0000035;
    }

    internal class Utility
    {
        private static uint FACILITY_NT_BIT = 0x10000000;

        private static int _GlobalId = 0;

        public static int GetNextId()
        {
            return ++_GlobalId;
        }

        /// <summary>
        /// Builds full path by smart adding root path and relative path
        /// </summary>
        /// <param name="strRootPath"></param>
        /// <param name="strRelativePath"></param>
        /// <returns></returns>
        public static string ComposePath(string strRootPath, string strRelativePath)
        {
            if (strRelativePath == "")
            {
                // Root directory itself
                return strRootPath;
            }
            else if (strRelativePath[0] == ':')
            {
                // Stream
                return strRootPath + strRelativePath;
            }
            else
            { 
                // Child file
                return strRootPath + @"\" + strRelativePath;
            }
        }

        /// <summary>
        /// Converts DateTime to FILETIME
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static System.Runtime.InteropServices.ComTypes.FILETIME FromDateTimeToFileTime(DateTime dateTime)
        {
            long lTime = dateTime.ToFileTime();

            System.Runtime.InteropServices.ComTypes.FILETIME time = new System.Runtime.InteropServices.ComTypes.FILETIME();

            // Just split 64 bit value into two 32 bit ones
            time.dwLowDateTime = (int)(lTime & 0xffffffff);
            time.dwHighDateTime = (int)(lTime >> 32);

            return time;
        }

        /// <summary>
        /// Converts FILETIME to int64
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static UInt64 FileTimeToInt64(System.Runtime.InteropServices.ComTypes.FILETIME time)
        {
            return ((UInt64)time.dwHighDateTime << 32) + (UInt64)time.dwLowDateTime;
        }

        /// <summary>
        /// Converts from NTSTATUS to HRESULT
        /// </summary>
        /// <param name="status"></param>
        /// <returns></returns>
        public static uint HRESULT_FROM_NT(uint status)
        {
            return status | FACILITY_NT_BIT;
        }
    }

    /// <summary>
    /// File node of virtual file system
    /// </summary>
    internal class Node
    {
        /// <summary>
        /// Parent node; null for root node
        /// </summary>
        public Node _Parent;

        /// <summary>
        /// Name of the file
        /// </summary>
        public string _Name;

        /// <summary>
        /// true for directories
        /// </summary>
        public bool _Directory;

        /// <summary>
        /// File attributes
        /// </summary>
        private UInt32 _Attributes;

        public UInt32 Attributes
        {
            get 
            {
                return _Attributes | (_Directory ? (UInt32)FileAttributes.Directory : 0);
            }
            set 
            {
                _Attributes = value;
            }
        }

        /// <summary>
        /// Child files; null for non directories
        /// </summary>
        public Dictionary<string, Node> _Childs = null;

        /// <summary>
        /// Stream that contains data of the file; null for directories
        /// </summary>
        public IStream _Stream = null;

        /// <summary>
        /// The file should be deleted (i.e. removed from parent child file list) when last its handle has closed
        /// </summary>
        public bool _DeleteOnClose = false;

        /// <summary>
        /// Is file deleted (i.e. removed from parent child file list)
        /// </summary>
        public bool _Deleted = false;

        /// <summary>
        /// A number of handles that refer to this file
        /// </summary>
        public long _HandleCount = 0;

        /// <summary>
        /// Creation time
        /// </summary>
        public System.Runtime.InteropServices.ComTypes.FILETIME _CreationTime = Utility.FromDateTimeToFileTime(DateTime.Now);

        /// <summary>
        /// The time of last write operation
        /// </summary>
        public System.Runtime.InteropServices.ComTypes.FILETIME _LastWriteTime = Utility.FromDateTimeToFileTime(DateTime.Now);

        /// <summary>
        /// The time of last access to the file
        /// </summary>
        public System.Runtime.InteropServices.ComTypes.FILETIME _LastAccessTime = Utility.FromDateTimeToFileTime(DateTime.Now);

        /// <summary>
        /// The time of last file change
        /// </summary>
        public System.Runtime.InteropServices.ComTypes.FILETIME _ChangeTime = Utility.FromDateTimeToFileTime(DateTime.Now);

        /// <summary>
        /// Create new node
        /// </summary>
        /// <param name="Parent"></param>
        /// <param name="Directory"></param>
        /// <param name="Name"></param>
        /// <param name="Attributes"></param>
        public Node(Node Parent, bool Directory, string Name, UInt32 Attributes)
        {
            // Save parent node and attributes
            _Parent = Parent;
            _Name = Name;
            _Directory = Directory;
            _Attributes = Attributes;

            if (Directory)
            {
                // Create child file map for directory file
                _Childs = new Dictionary<string, Node>();
            }
            else
            {
                // Create data stream for non directory file
                _Stream = new StreamImpl(this, new MemoryStream());
            }

            // The node should be added to parent node child file list
            if (Parent != null)
            { 
                Parent._Childs[Name] = this;
            }
        }

        public void put_Attributes(UInt32 newAttributes)
        {
            _Attributes = newAttributes;
        }
    }

    /// <summary>
    /// IStream implementation based on MemoryStream
    /// </summary>
    internal class StreamImpl : IStream
    {
        /// <summary>
        /// The node which the stream refers to
        /// </summary>
        private Node _Node;

        /// <summary>
        /// Base MemoryStream
        /// </summary>
        private MemoryStream _Stream;

        /// <summary>
        /// Current position within MemoryStream
        /// </summary>
        private int _Position;

        /// <summary>
        /// Creates new IStream implementation
        /// </summary>
        /// <param name="Stream"></param>
        public StreamImpl(Node Node, MemoryStream MemoryStream)
        {
            _Node = Node;
            _Stream = MemoryStream;
            _Position = 0;
        }

        #region IStream Members

        /// <summary>
        /// Reads data from stream starting from current position
        /// </summary>
        /// <param name="pv"></param>
        /// <param name="cb"></param>
        /// <param name="pcbRead"></param>
        public void Read(byte[] pv, int cb, IntPtr pcbRead)
        {
            _Stream.Position = _Position;
            int nReadBytes = _Stream.Read(pv, 0, cb);

            _Position += nReadBytes;

            if (IntPtr.Zero != pcbRead)
                Marshal.WriteIntPtr(pcbRead, new IntPtr(nReadBytes));
        }

        /// <summary>
        /// Writes data to stream starting from current position
        /// </summary>
        /// <param name="pv"></param>
        /// <param name="cb"></param>
        /// <param name="pcbWritten"></param>
        public void Write(byte[] pv, int cb, IntPtr pcbWritten)
        {
            _Stream.Position = _Position;
            _Stream.Write(pv, 0, cb);

            _Position += cb;

            if (IntPtr.Zero != pcbWritten)
                Marshal.WriteIntPtr(pcbWritten, new IntPtr(cb));
        }

        /// <summary>
        /// Creates another IStream implementation that refers to the same data but has its own current position
        /// </summary>
        /// <param name="ppstm"></param>
        public void Clone(out IStream ppstm)
        {
            ppstm = new StreamImpl(_Node, _Stream);
        }

        /// <summary>
        /// Changes current position
        /// </summary>
        /// <param name="dlibMove"></param>
        /// <param name="dwOrigin"></param>
        /// <param name="plibNewPosition"></param>
        public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
        {
            SeekOrigin Origin = (SeekOrigin)dwOrigin;

            switch (Origin)
            {
                case SeekOrigin.Begin:
                {
                    _Position = (int)dlibMove;
                    break;
                }
                
                case SeekOrigin.Current:
                {
                    _Position += (int)dlibMove;
                    break;
                }

                case SeekOrigin.End:
                {
                    _Position = (int)_Stream.Length + (int)dlibMove;
                    break;
                }
            }

            if (IntPtr.Zero != plibNewPosition)
            { 
                Marshal.WriteInt64(plibNewPosition, _Position);
            }
        }

        public void Commit(int grfCommitFlags)
        {
            throw new NotImplementedException();
        }

        public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
        {
            throw new NotImplementedException();
        }

        public void LockRegion(long libOffset, long cb, int dwLockType)
        {
            throw new NotImplementedException();
        }

        public void Revert()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Changes size of the stream
        /// </summary>
        /// <param name="libNewSize"></param>
        public void SetSize(long libNewSize)
        {
            _Stream.SetLength(libNewSize);
        }

        /// <summary>
        /// Returns stream information
        /// </summary>
        /// <param name="pstatstg"></param>
        /// <param name="grfStatFlag"></param>
        public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
        {
            pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();

            pstatstg.cbSize = _Stream.Length;
            pstatstg.type = 0;

            if (Consts.STATFLAG_NONAME == grfStatFlag)
            {
                pstatstg.pwcsName = null;
            }
            else
            {
                pstatstg.pwcsName = _Node._Name;
            }

            pstatstg.cbSize = _Stream.Length;
            pstatstg.atime = _Node._LastAccessTime;
            pstatstg.ctime = _Node._CreationTime;
            pstatstg.mtime = _Node._ChangeTime;
        }

        public void UnlockRegion(long libOffset, long cb, int dwLockType)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    /// <summary>
    /// Handle of a file
    /// </summary>
    internal class NodeHandle : NativeMethods.IBxFileHandle, NativeMethods.IBxFileHandle2
    {
        /// <summary>
        /// A node to which the handle refers to
        /// </summary>
        public Node _Node;
        
        /// <summary>
        /// A stream with its own current position; for a handle of non directory file
        /// </summary>
        public IStream _Stream = null;

        /// <summary>
        /// Child file enumeration was not called yet?
        /// </summary>
        private bool _EnumFirstTime = true;

        /// <summary>
        /// Enumerator of child files
        /// </summary>
        private Dictionary<string, Node>.KeyCollection.Enumerator _ChildEnumerator;

        /// <summary>
        /// id for debugging purpose
        /// </summary>
        private int _Id = Utility.GetNextId();

        /// <summary>
        /// Creates new handle object
        /// </summary>
        /// <param name="Node"></param>
        public NodeHandle(Node Node)
        {
            _Node = Node;

            if (!Node._Directory)
            {
                Node._Stream.Clone(out _Stream);
            }
        }

        /// <summary>
        /// This method is called when new system handle is allocated for this handle object
        /// </summary>
        public void onNewHandle()
        {
            _Node._HandleCount++;

            Console.WriteLine("Handle of '" + _Node._Name + "' (" + _Id + ") created, new handle count is " + _Node._HandleCount);
        }

        /// <summary>
        /// This method is called when system handle (associated with this handle object) is closed
        /// </summary>
        public void onCloseHandle()
        {
            // It's important to keep handle count, because if file is marked as "delete on close" and
            // handle count is zero, then we have to remove the file

            _Node._HandleCount--;

            Console.WriteLine("Handle of '" + _Node._Name + "' (" + _Id + ") closed, new handle count is " + _Node._HandleCount);

            if (_Node._DeleteOnClose && 0 == _Node._HandleCount && !_Node._Deleted)
            { 
                // File should be deleted when last handle closed (if it was not removed already)
                bool res = _Node._Parent._Childs.Remove(_Node._Name);

                Console.WriteLine("'" + _Node._Name + "' (" + _Id + ") removed");
            }
        }

        /// <summary>
        /// Returns stream associated with the handle
        /// </summary>
        /// <returns></returns>
        public IStream get_Stream()
        {
            return _Stream;
        }

        /// <summary>
        /// Returns attributes of the file
        /// </summary>
        /// <returns></returns>
        public UInt32 get_Attributes()
        {
            return _Node.Attributes;
        }

        /// <summary>
        /// Sets new attributes of the file
        /// </summary>
        /// <param name="Attributes"></param>
        public void put_Attributes(UInt32 Attributes)
        {
            _Node.Attributes = Attributes;
        }

        /// <summary>
        /// Returns whether the file should be deleted when its last handle closed
        /// </summary>
        /// <returns></returns>
        public bool get_DeleteOnClose()
        {
            return _Node._DeleteOnClose;
        }

        /// <summary>
        /// Marks or unmarks the file as "delete on close" (i.e. remove file when its last handle closed)
        /// </summary>
        /// <param name="bDeleteOnClose"></param>
        public void put_DeleteOnClose(bool bDeleteOnClose)
        {
            Console.WriteLine("put_DeleteOnClose: '" + _Node._Name + "' (" + _Id + "), " + bDeleteOnClose.ToString());

            _Node._DeleteOnClose = bDeleteOnClose;
        }

        public System.Runtime.InteropServices.ComTypes.FILETIME get_CreationTime()
        {
            return _Node._CreationTime;
        }

        public void put_CreationTime(System.Runtime.InteropServices.ComTypes.FILETIME newtime)
        {
            _Node._CreationTime = newtime;
        }

        public System.Runtime.InteropServices.ComTypes.FILETIME get_LastWriteTime()
        {
            return _Node._LastWriteTime;
        }

        public void put_LastWriteTime(System.Runtime.InteropServices.ComTypes.FILETIME newtime)
        {
            _Node._LastWriteTime = newtime;
        }

        public System.Runtime.InteropServices.ComTypes.FILETIME get_LastAccessTime()
        {
            return _Node._LastAccessTime;
        }

        public void put_LastAccessTime(System.Runtime.InteropServices.ComTypes.FILETIME newtime)
        {
            _Node._LastAccessTime = newtime;
        }

        public System.Runtime.InteropServices.ComTypes.FILETIME get_ChangeTime()
        {
            return _Node._ChangeTime;
        }

        public void put_ChangeTime(System.Runtime.InteropServices.ComTypes.FILETIME newtime)
        {
            _Node._ChangeTime = newtime;
        }

        public void queryStreamInfo(
            UInt32 nRequested, 
            IntPtr props, 
            out UInt32 nFetched)
        {
            nFetched = 0;

            /*
            // Here the sample of how to return stream information
            if (nRequested == 1)
            {
                NativeMethods.SBxStreamProp prop = new NativeMethods.SBxStreamProp();

                prop.Name = "test";
                prop.Size = 1;
                prop.SizeOfStruct = (uint)Marshal.SizeOf(prop);

                Marshal.StructureToPtr(prop, props, true);

                nFetched = 1;
            }
            else 
            {
                nFetched = 0;
            }
            */
        }

        /// <summary>
        /// Returns information about next child file
        /// </summary>
        /// <param name="bRestart"></param>
        /// <param name="prop"></param>
        public void get_NextFile(bool bRestart, out NativeMethods.SBxFileProp prop)
        {
            if (bRestart || _EnumFirstTime)
            {
                _EnumFirstTime = false;
                _ChildEnumerator = _Node._Childs.Keys.GetEnumerator();
            }

            _ChildEnumerator.MoveNext();

            if (_ChildEnumerator.Current == null)
            {
                throw new COMException(null, Consts.S_FALSE);
            }

            string Name = _ChildEnumerator.Current;
            Node Node = _Node._Childs[Name];

            prop.SizeOfStruct = (uint)Marshal.SizeOf(typeof(NativeMethods.SBxFileProp));
            prop.Attributes = Node.Attributes;
            prop.ChangeTime = Node._ChangeTime;
            prop.CreationTime = Node._CreationTime;
            prop.LastAccessTime = Node._LastAccessTime;
            prop.LastWriteTime = Node._LastWriteTime;
            prop.Name = Name;

            if (Node._Directory)
            {
                prop.Size = 0;
            }
            else 
            {
                System.Runtime.InteropServices.ComTypes.STATSTG stat;
                Node._Stream.Stat(out stat, Consts.STATFLAG_NONAME);
                prop.Size = stat.cbSize;
            }
        }

        public void rename(string strNewRelativePath, bool bReplaceIfExists)
        {
            if (strNewRelativePath.Contains("\\"))
            {
                //TODO
            }
            else
            {
                string strNewName = strNewRelativePath;
                string strOldName = _Node._Name;

                if (_Node._Parent._Childs.ContainsKey(strNewName))
                {
                    if (!bReplaceIfExists)
                    {
                        throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_NAME_COLLISION));
                    }

                    _Node._Parent._Childs[strNewName]._Deleted = true;
                    _Node._Parent._Childs.Remove(strNewName);
                }

                _Node._Parent._Childs.Remove(strOldName);
                _Node._Parent._Childs.Add(strNewName, _Node);
                _Node._Name = strNewName;

                Console.WriteLine("File '" + strOldName + "' renamed to '" + _Node._Name + "' (" + _Id + ")");
            }
        }
    }

    /// <summary>
    /// Helper to iterate over path
    /// </summary>
    internal class PathIterator
    {
        /// <summary>
        /// Path components of the path
        /// </summary>
        private string[] _PathComponents;

        /// <summary>
        /// Current position within the path
        /// </summary>
        private int _CurrentComponent;

        public PathIterator(string RelativePath)
        {
            if (RelativePath.Length == 0)
            {
                _PathComponents = null;
                _CurrentComponent = 0;
            }
            else
            { 
                _PathComponents = RelativePath.Split('\\');
                _CurrentComponent = 0;
            }
        }

        /// <summary>
        /// Returns current position (to restore it later using RestorePosition)
        /// </summary>
        /// <returns></returns>
        public int SavePosition()
        {
            return _CurrentComponent;
        }

        /// <summary>
        /// Rertores current position
        /// </summary>
        /// <param name="SavedPosition"></param>
        public void RestorePosition(int SavedPosition)
        {
            _CurrentComponent = SavedPosition;
        }

        /// <summary>
        /// Returns next path component if any; returns null if no more path components
        /// </summary>
        /// <returns></returns>
        public string GetNextComponent()
        {
            if (null == _PathComponents || _CurrentComponent >= _PathComponents.GetLength(0))
            {
                return null;
            }

            return _PathComponents[_CurrentComponent++];
        }
    }

    /// <summary>
    /// Virtual directory located in the memory
    /// </summary>
    class CustomVirtualDirectory : NativeMethods.IBxFile
    {
        /// <summary>
        /// Root node of the hierarchy
        /// </summary>
        private Node _Node = new Node(null, true, "", (UInt32)FileAttributes.Directory);

        public CustomVirtualDirectory()
        {
        }

        /// <summary>
        /// Tries to search node in the file tree
        /// </summary>
        /// <param name="strRelativePath"></param>
        /// <param name="it"></param>
        /// <param name="LastFoundNode"></param>
        private void SearchNode(string strRelativePath, out PathIterator it, out Node LastFoundNode)
        {
            it = new PathIterator(strRelativePath);

            LastFoundNode = _Node;

            while (true)
            { 
                int SavedPos = it.SavePosition();

                string Component = it.GetNextComponent();

                if (Component != null)
                {
                    Node ChildNode;

                    if (LastFoundNode._Childs.TryGetValue(Component, out ChildNode))
                    {
                        LastFoundNode = ChildNode;
                        continue;
                    }
                    else
                    {
                        it.RestorePosition(SavedPos);
                        return;
                    }
                }
                else
                {
                    return;
                }
            }
        }

        private Node FindExactNode(string strRelativePath)
        {
            PathIterator it;
            Node LastFoundNode;

            SearchNode(strRelativePath, out it, out LastFoundNode);

            string Component = it.GetNextComponent();

            if (Component != null)
            {
                Component = it.GetNextComponent();

                if (Component != null)
                    throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_PATH_NOT_FOUND));
                else
                    throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_NAME_NOT_FOUND));
            }

            return LastFoundNode;
        }

        public NativeMethods.IBxFileHandle createFile(
            string strRelativePath,
            UInt32 DesiredAccess,
            UInt32 Attributes,
            UInt32 ShareAccess,
            uint CreateOptions)
        {
            Console.WriteLine("createFile: '" + strRelativePath + "'");

            PathIterator it;
            Node LastFoundNode;

            SearchNode(strRelativePath, out it, out LastFoundNode);

            string Component = it.GetNextComponent();

            if (Component != null)
            {
                if (null != it.GetNextComponent())
                    // Path not found
                    throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_PATH_NOT_FOUND));

                // Create new file and return handle of the just created file
                return new NodeHandle(new Node(LastFoundNode, 0 != (CreateOptions & Consts.FILE_DIRECTORY_FILE), Component, Attributes));
            }

            // File found in the tree
            throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_NAME_COLLISION));
        }

        public NativeMethods.IBxFileHandle openFile(
            string strRelativePath,
            UInt32 DesiredAccess,
            UInt32 ShareAccess,
            UInt32 CreateOptions)
        {
            Console.WriteLine("openFile: '" + strRelativePath + "'");

            PathIterator it;
            Node LastFoundNode;

            SearchNode(strRelativePath, out it, out LastFoundNode);

            string Component = it.GetNextComponent();

            if (Component != null)
            {
                // File not found

                Component = it.GetNextComponent();

                if (Component != null)
                    throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_PATH_NOT_FOUND));
                else
                    throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_NAME_NOT_FOUND));
            }

            // File found in the tree
            return new NodeHandle(LastFoundNode);
        }

        public NativeMethods.IBxFileHandle replaceFile(
            string strRelativePath,
            UInt32 DesiredAccess,
            UInt32 Attributes,
            UInt32 ShareAccess,
            UInt32 CreateOptions)
        {
            Console.WriteLine("replaceFile: '" + strRelativePath + "'");

            PathIterator it;
            Node LastFoundNode;

            SearchNode(strRelativePath, out it, out LastFoundNode);

            string Component = it.GetNextComponent();

            if (Component != null)
            {
                if (null != it.GetNextComponent())
                    // Path not found
                    throw new COMException(null, (int)Utility.HRESULT_FROM_NT(Consts.STATUS_OBJECT_PATH_NOT_FOUND));
            }

            // Create new file and return handle of the just created file
            return new NodeHandle(new Node(LastFoundNode, 0 != (CreateOptions & Consts.FILE_DIRECTORY_FILE), Component, Attributes));
        }

        public UInt32 get_Attributes(string strRelativePath)
        {
            return FindExactNode(strRelativePath).Attributes;
        }

        public void put_Attributes(
            string strRelativePath,
            UInt32 Attributes)
        {
            FindExactNode(strRelativePath).Attributes = Attributes;
        }

        public Int64 get_Size(string strRelativePath)
        {
            Node Node = FindExactNode(strRelativePath);

            if (Node._Directory)
            {
                return 0;
            }
            else 
            {
                System.Runtime.InteropServices.ComTypes.STATSTG info;
                Node._Stream.Stat(out info, Consts.STATFLAG_NONAME);

                return info.cbSize;
            }
        }

        public Int64 get_CreationTime(string strRelativePath)
        {
            return (Int64)Utility.FileTimeToInt64(FindExactNode(strRelativePath)._CreationTime);
        }

        public Int64 get_LastWriteTime(string strRelativePath)
        {
            return (Int64)Utility.FileTimeToInt64(FindExactNode(strRelativePath)._LastWriteTime);
        }

        public Int64 get_LastAccessTime(string strRelativePath)
        {
            return (Int64)Utility.FileTimeToInt64(FindExactNode(strRelativePath)._LastAccessTime);
        }

        public Int64 get_ChangeTime(string strRelativePath)
        {
            return (Int64)Utility.FileTimeToInt64(FindExactNode(strRelativePath)._ChangeTime);
        }
    }
}
