Example #1
0
        private OperationLogMode mode;                          // The current mode

        /// <summary>
        /// Opens or creates a file operation log file.
        /// </summary>
        /// <param name="path">The path to the log file.</param>
        /// <param name="transactionID">The log's transaction <see cref="Guid" />.</param>
        /// <remarks>
        /// New logs will be created in <see cref="OperationLogMode.Undo" /> mode.
        /// </remarks>
        public FileOperationLog(string path, Guid transactionID)
        {
            this.path = Path.GetFullPath(path);
            this.file = new EnhancedFileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite);

            if (file.Length == 0)
            {
                // Initialize a new file

                file.WriteInt32(Magic);
                file.WriteInt32(0);
                file.WriteInt32(0);
                file.WriteInt32((int)OperationLogMode.Undo);
                file.WriteBytesNoLen(transactionID.ToByteArray());
                file.Flush();
            }
            else
            {
                // Open an existing file.

                try
                {
                    if (file.ReadInt32() != Magic || // Magic number
                        file.ReadInt32() != 0)
                    {                                // Format Versopn
                        throw new Exception();
                    }

                    file.ReadInt32();                   // Reserved

                    switch (file.ReadInt32())           // Mode
                    {
                    case (int)OperationLogMode.Undo:

                        mode = OperationLogMode.Undo;
                        break;

                    case (int)OperationLogMode.Redo:

                        mode = OperationLogMode.Redo;
                        break;

                    default:

                        throw new Exception();
                    }

                    this.transactionID = new Guid(file.ReadBytes(16));
                    if (transactionID != this.transactionID)
                    {
                        throw new Exception();
                    }
                }
                catch
                {
                    throw new TransactionException(CorruptMsg);
                }
            }
        }
Example #2
0
        /// <summary>
        /// Reads the metadata from a message file.
        /// </summary>
        /// <param name="msgID">The message ID.</param>
        /// <param name="path">Fully qualified path to the message file.</param>
        /// <returns>The <see cref="QueuedMsgInfo" />.</returns>
        internal QueuedMsgInfo ReadMessageMetadata(Guid msgID, string path)
        {
            QueuedMsgInfo msgInfo;
            int           cbBody;

            byte[] md5Hash;
            long   savePos;

            using (var fsMsg = new EnhancedFileStream(path, FileMode.Open, FileAccess.ReadWrite))
            {
                try
                {
                    // Read the message file header

                    if (fsMsg.ReadInt32() != MsgMagic)      // Magic Number
                    {
                        throw new Exception();
                    }

                    if (fsMsg.ReadInt32() != 0)             // Format Version
                    {
                        throw new Exception();
                    }

                    fsMsg.ReadInt32();                      // Reserved

                    // Verify that the MD5 hash saved in then file matches the
                    // hash computed for the remainder of the file as it exists
                    // right now.

                    md5Hash = fsMsg.ReadBytes(MD5Hasher.DigestSize);
                    savePos = fsMsg.Position;

                    if (!Helper.ArrayEquals(md5Hash, MD5Hasher.Compute(fsMsg, fsMsg.Length - fsMsg.Position)))
                    {
                        throw new FormatException(string.Format("Message file [{0}] is corrupt. MD5 digests do not match.", path));
                    }

                    fsMsg.Position = savePos;

                    // Skip over the message body data

                    cbBody         = fsMsg.ReadInt32();
                    fsMsg.Position = fsMsg.Position + cbBody;

                    // Read the metadata and add the provider specific information

                    msgInfo              = new QueuedMsgInfo(fsMsg.ReadString16());
                    msgInfo.PersistID    = msgInfo.ID;
                    msgInfo.ProviderData = path;

                    return(msgInfo);
                }
                catch
                {
                    throw new FormatException(string.Format("Bad message file [{0}].", path));
                }
            }
        }
Example #3
0
        /// <summary>
        /// Updates a message file's metadata.
        /// </summary>
        /// <param name="path">The file path.</param>
        /// <param name="msgInfo">The message metadata.</param>
        internal void WriteMessageMetadata(string path, QueuedMsgInfo msgInfo)
        {
            using (var fsMsg = new EnhancedFileStream(path, FileMode.Open, FileAccess.ReadWrite))
            {
                // Seek past the message's header and body.

                int    cbBody;
                byte[] md5Hash;

                fsMsg.Position = MsgHeaderSize;
                cbBody         = fsMsg.ReadInt32();
                fsMsg.Position = fsMsg.Position + cbBody;

                // Write the metadata and truncate the file.

                fsMsg.WriteString16(msgInfo.ToString());
                fsMsg.SetLength(fsMsg.Position);

                // Regenerate the MD5 Hash

                fsMsg.Position = MsgMD5Offset + MD5Hasher.DigestSize;
                md5Hash        = MD5Hasher.Compute(fsMsg, fsMsg.Length - fsMsg.Position);
                fsMsg.Position = MsgMD5Offset;
                fsMsg.WriteBytesNoLen(md5Hash);
            }
        }
Example #4
0
        //---------------------------------------------------------------------
        // Static members

        /// <summary>
        /// Verifies that a log file is not corrupt.
        /// </summary>
        /// <param name="path">The path to the log file.</param>
        /// <param name="transactionID">Returns as the file's transaction <see cref="Guid" />.</param>
        /// <returns><c>true</c> if the log file is valid, <c>false</c> if it is corrupt.</returns>
        public static bool Validate(string path, out Guid transactionID)
        {
            int cb;

            try
            {
                using (var file = new EnhancedFileStream(path, FileMode.Open, FileAccess.ReadWrite))
                {
                    if (file.ReadInt32() != Magic || // Magic number
                        file.ReadInt32() != 0)
                    {                                // Format Versopn
                        throw new Exception();
                    }

                    switch (file.ReadInt32())           // Mode
                    {
                    case (int)OperationLogMode.Undo:
                    case (int)OperationLogMode.Redo:

                        break;

                    default:

                        throw new Exception();
                    }

                    file.ReadInt32();                   // Reserved
                    transactionID = new Guid(file.ReadBytes(16));

                    while (!file.Eof)
                    {
                        if (file.ReadInt32() != Magic)
                        {
                            throw new Exception();
                        }

                        cb = file.ReadInt32();
                        if (cb < 0 || cb + file.Position > file.Length)
                        {
                            throw new Exception();
                        }

                        file.Position += cb;
                    }

                    return(true);
                }
            }
            catch
            {
                transactionID = Guid.Empty;
                return(false);
            }
        }
Example #5
0
        /// <summary>
        /// Returns the list of the <see cref="ILogPosition" />s with each operation in the log.
        /// </summary>
        /// <param name="reverse">Pass <c>true</c> to return the positions in the reverse order that they were appended to the log.</param>
        /// <returns>The operation position list.</returns>
        /// <exception cref="TransactionException">Thrown if the log is not open or is corrupt.</exception>
        public List <ILogPosition> GetPositions(bool reverse)
        {
            var  list = new List <ILogPosition>();
            long length;
            int  cb;

            using (TimedLock.Lock(this))
            {
                if (file == null)
                {
                    throw new TransactionException(ClosedMsg);
                }

                length        = file.Length;
                file.Position = HeaderSize;

                while (!file.Eof)
                {
                    list.Add(new FileLogPosition(file.Position));

                    if (file.ReadInt32() != Magic)
                    {
                        throw new TransactionException(CorruptMsg);
                    }

                    cb = file.ReadInt32();
                    if (cb < 0 || cb + file.Position > length)
                    {
                        throw new TransactionException(CorruptMsg);
                    }

                    file.Position += cb;
                }
            }

            if (reverse)
            {
                list.Reverse();
            }

            return(list);
        }
Example #6
0
        /// <summary>
        /// Loads the message index file.  If the index does not exist or is corrupt then
        /// the index will be rebuilt by performing a full scan of message files under
        /// the root folder.
        /// </summary>
        /// <param name="newIndexFile">Pass as <c>true</c> if a new index file is being created.</param>
        private void LoadIndex(bool newIndexFile)
        {
            string formatErr = string.Format("Invalid or missing index file [{0}].", indexPath);
            int    cMessages;

            string[] files;
            Dictionary <Guid, string> msgFiles;

            TimedLock.AssertLocked(this);
            messages = new Dictionary <Guid, QueuedMsgInfo>();

            // List the message files and build a hash table mapping each message GUID
            // the fully qualified path to the file.

            files    = Helper.GetFilesByPattern(root + "*.msg", SearchOption.AllDirectories);
            msgFiles = new Dictionary <Guid, string>(files.Length);

            foreach (string path in files)
            {
                string file = Path.GetFileName(path).ToLowerInvariant();
                Guid   msgID;

                if (!file.EndsWith(".msg"))
                {
                    continue;
                }

                try
                {
                    msgID = new Guid(file.Substring(0, file.Length - 4));
                }
                catch
                {
                    continue;
                }

                msgFiles[msgID] = path;
            }

            // Load the index file.

            try
            {
                // Parse the index file header

                fsIndex.Position = 0;
                if (fsIndex.ReadInt32() != IndexMagic)      // Magic Number
                {
                    throw new FormatException(formatErr);
                }

                if (fsIndex.ReadInt32() != 0)               // Format Version
                {
                    throw new FormatException(formatErr);
                }

                fsIndex.ReadInt32();                        // Reserved

                if (fsIndex.ReadInt32() != 0)
                {
                    throw new FormatException(string.Format("Index file [{0}] was not closed properly. Full message folder scan will be performed.", indexPath));
                }

                cMessages = fsIndex.ReadInt32();            // Message Count

                // Parse the message metadata

                for (int i = 0; i < cMessages; i++)
                {
                    QueuedMsgInfo msgInfo;

                    msgInfo              = new QueuedMsgInfo(fsIndex.ReadString16());
                    msgInfo.PersistID    = msgInfo.ID;
                    msgInfo.ProviderData = root + fsIndex.ReadString16();   // Make the paths absolute

                    messages[msgInfo.ID] = msgInfo;
                }

                // Perform an extra consistency check by listing all of the message files
                // under the root folder and comparing the message GUIDs encoded into the
                // file names with the GUIDs loaded from the index file and then bringing
                // the index into sync with the actual message files.

                bool updated = false;
                int  cLoaded = 0;

                // Delete any metadata for messages that exist in the index
                // but don't exist on the file system.

                var delList = new List <Guid>();

                foreach (Guid msgID in messages.Keys)
                {
                    if (!msgFiles.ContainsKey(msgID))
                    {
                        delList.Add(msgID);
                    }
                }

                foreach (Guid msgID in delList)
                {
                    messages.Remove(msgID);
                }

                if (delList.Count > 0)
                {
                    updated = true;
                    SysLog.LogWarning(string.Format("Message index [{0}] has message metadata for messages that do not exist. [{1}] messages will be removed from the index.", indexPath, delList.Count));
                }

                // Load metadata for messages that exist in the file system
                // but were not in the index.

                foreach (Guid msgID in msgFiles.Keys)
                {
                    if (!messages.ContainsKey(msgID))
                    {
                        string path = msgFiles[msgID];

                        try
                        {
                            messages[msgID] = ReadMessageMetadata(msgID, path);
                            cLoaded++;
                        }
                        catch (Exception e)
                        {
                            SysLog.LogException(e);
                        }
                    }
                }

                if (cLoaded > 0)
                {
                    updated = true;
                    SysLog.LogWarning(string.Format("Message index [{0}] is missing metadata for [{1}] messages. Missing entries will be added.", indexPath, cLoaded));
                }

                if (updated)
                {
                    SaveIndex(true);
                }

                // Mark the index as "open" for crash detection.

                fsIndex.Position = OpenFlagOffset;
                fsIndex.WriteInt32(1);
                fsIndex.Flush();
            }
            catch
            {
                if (newIndexFile)
                {
                    SysLog.LogWarning("Rebuilding missing message index file [{0}].", indexPath);
                }
                else
                {
                    SysLog.LogWarning("Rebuilding corrupt message index file [{0}].", indexPath);
                }

                // Clear the index file if there was a serious error and then
                // rebuild it from scratch by scanning the message metadata.

                fsIndex.SetLength(0);
                fsIndex.Flush();
                messages.Clear();

                foreach (Guid msgID in msgFiles.Keys)
                {
                    try
                    {
                        messages.Add(msgID, ReadMessageMetadata(msgID, msgFiles[msgID]));
                    }
                    catch (Exception e2)
                    {
                        SysLog.LogException(e2);
                    }
                }

                // Save the index, marking the file as "open" for crash detection

                SaveIndex(true);
            }
        }
Example #7
0
        /// <summary>
        /// Loads the cache index file.
        /// </summary>
        private void LoadIndex()
        {
            var indexPath = Path.Combine(settings.PhraseCacheFolder, IndexFileName);

            try
            {
                if (!File.Exists(indexPath))
                {
                    // No index exists yet, so create an empty one.

                    SaveIndex(false);
                    return;
                }

                using (var fs = new EnhancedFileStream(indexPath, FileMode.Open))
                {
                    int count;

                    if (fs.ReadInt32() != Magic)
                    {
                        throw new SwitchException("[PhraseCache] cannot read index file [{0}] due to an invalid magic number.  The existing cache will be purged.", indexPath);
                    }

                    count = fs.ReadInt32();
                    for (int i = 0; i < count; i++)
                    {
                        string        text;
                        string        voice;
                        string        actualVoice;
                        PhraseType    phraseType;
                        TtsEncoding   encoding;
                        TtsSampleRate rate;
                        DateTime      lastAccessUtc;
                        string        path;
                        Phrase        phrase;

                        phraseType    = (PhraseType)Enum.Parse(typeof(PhraseType), fs.ReadString16());
                        text          = fs.ReadString32();
                        voice         = fs.ReadString16();
                        actualVoice   = fs.ReadString16();
                        encoding      = (TtsEncoding)Enum.Parse(typeof(TtsEncoding), fs.ReadString16());
                        rate          = (TtsSampleRate)Enum.Parse(typeof(TtsSampleRate), fs.ReadString16());
                        lastAccessUtc = new DateTime(fs.ReadInt64());
                        path          = fs.ReadString16();

                        phrase = new Phrase(phraseType, voice, encoding, rate, text)
                        {
                            Path          = path,
                            ActualVoice   = actualVoice,
                            LastAccessUtc = lastAccessUtc,
                        };

                        index[GetCacheKey(phrase)] = phrase;
                    }
                }
            }
            catch (Exception e)
            {
                // We're going to handle all exceptions leaving the loaded phrase
                // table empty.  This will have the effect of starting the cache
                // from scratch.  Eventually, all of the existing files will be
                // purged and the presumably bad index file will be overwritten.

                index.Clear();
                SysLog.LogException(e);
            }
        }