/// <summary>
        /// Writes the <paramref name="source"/> instance, of type <typeparamref name="T"/>, as binary data to the file with the given <paramref name="rootFilename"/> in the specified <paramref name="fileStore"/>.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> of the instance.</typeparam>
        /// <param name="source">The instance to save.</param>
        /// <param name="fileStore">The <see cref="FileStorage.IFileStore"/> to save the file into.</param>
        /// <param name="rootFilename">The name of the file to save the binary data to.</param>
        public void Save <T>(T source, FileStorage.IFileStore fileStore, string rootFilename)
        {
            // check for errors
            if (string.IsNullOrWhiteSpace(rootFilename))
            {
                throw new ArgumentNullException(nameof(rootFilename));
            }
            if (fileStore == null)
            {
                throw new ArgumentNullException(nameof(fileStore));
            }
            // save T to file
            var tempFilename = System.IO.Path.GetTempFileName();

            try
            {
                // save file to temp file
                Save(source, tempFilename, true);
                // add temp file to file store
                var fileInfo = new System.IO.FileInfo(tempFilename);
                fileStore.Add(rootFilename, tempFilename, fileInfo.LastWriteTimeUtc, fileInfo.Length);
                fileStore.Save(false);
            }
            finally
            {
                // delete temp file
                if (System.IO.File.Exists(tempFilename))
                {
                    System.IO.File.Delete(tempFilename);
                }
            }
        }
 /// <summary>
 /// Will save the package file store, clean up residual files and close the package.
 /// </summary>
 /// <remarks>
 /// <para>The package, which should be some form of zip file, has been extracted from the package file store and automatically connected to. Which means it must be disconnected and deleted when finished.
 /// Therefore, it is imperative that the <see cref="Close"/> method is called so it can save the package file store and disconnect from it; allowing the <see cref="PackageProvider"/> to delete the extracted zip file, and any other residual files.</para>
 /// <para>This type implements <see cref="IDisposable"/>, best practice would be to use a <b>using(...)</b> statement. Otherwise, you must call <see cref="Close"/>.</para>
 /// </remarks>
 public void Close()
 {
     if (IsOpen)
     {
         try
         {
             // save package zip file store before it gets added to the packages file store
             _FileStore.Save(false);
             // add package zip file to packages store
             _PackagesStore.Add(_PackageRootFilename, _OriginalFilename, DateTime.Now, _FileStore.SizeInBytes);
             // save packages store
             _PackagesStore.Save(false);
         }
         finally
         {
             // delete the original FileStore file
             try { if (System.IO.File.Exists(_OriginalFilename))
                   {
                       System.IO.File.Delete(_OriginalFilename);
                   }
             }
             catch { }
             // set flag to closed
             IsOpen = false;
         }
     }
 }
        /// <summary>
        /// Will copy all logs, which returns <b>true</b> from the <paramref name="logEntryFilterPredicate"/> function, from the <paramref name="log"/> and write them to a text file with the <paramref name="filename"/> in the given <paramref name="fileStore"/>.
        /// </summary>
        /// <remarks>
        /// <code>
        /// // The following characters are encoded into, and decoded from, the following:
        /// //      Percent        (%)  %&lt;25&gt;
        /// //      Semicolon      (;)  %&lt;3B&gt;
        /// //      Double-Quote   (")  %&lt;22&gt;
        /// //      Newline        ( )  %&lt;0D&gt;
        /// </code>
        /// </remarks>
        /// <param name="log">The logger to read log from.</param>
        /// <param name="fileStore">The file store to write the log file to.</param>
        /// <param name="filename">The name of the file in the file store to write the logs to.</param>
        /// <param name="writeLogEntriesUsingLocalTime">Sets whether the date is written to the log relative to UTC or the local computer's timezone.</param>
        /// <param name="truncateLogArchive">Sets whether the <paramref name="fileStore"/> will be automatically truncated.</param>
        /// <param name="truncateLogArchiveMaximumFiles">The total number of files allowed in the <paramref name="fileStore"/> before auto truncation is performed.</param>
        /// <param name="truncateLogArchivePercentageToRemove">The percentage of log archive files to remove once <paramref name="truncateLogArchiveMaximumFiles"/> has been exceeded.</param>
        /// <param name="logEntryFilterPredicate">The <see cref="Func{T, TResult}"/> should return <b>true</b> to write the log to the archive file; return <b>false</b> to skip writing the log.</param>
        /// <returns>The number of logs written to the log file.</returns>
        public static int ArchiveLogs(ILogReader log,
                                      FileStorage.IFileStore fileStore,
                                      string filename,
                                      bool writeLogEntriesUsingLocalTime,
                                      bool truncateLogArchive,
                                      int truncateLogArchiveMaximumFiles,
                                      double truncateLogArchivePercentageToRemove,
                                      Func <ILogEntry, bool> logEntryFilterPredicate)
        {
            if (log == null)
            {
                throw new ArgumentNullException(nameof(log));
            }
            if (fileStore == null)
            {
                throw new ArgumentNullException(nameof(fileStore));
            }
            if (string.IsNullOrWhiteSpace(filename))
            {
                throw new ArgumentNullException(nameof(filename));
            }
            if ((truncateLogArchivePercentageToRemove < 0) || (truncateLogArchivePercentageToRemove > 1))
            {
                throw new ArgumentOutOfRangeException(nameof(truncateLogArchivePercentageToRemove), $"Parameter truncateLogArchivePercentageToRemove must be between 0.0 and 1.0. Value={truncateLogArchivePercentageToRemove}");
            }
            if (logEntryFilterPredicate == null)
            {
                throw new ArgumentNullException(nameof(logEntryFilterPredicate));
            }
            //
            var archivedLogs = 0;
            // copy logs from the logger to the temp file
            var tempFilename = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());

            lock (log.SyncObject)
            {
                using (var sw = new System.IO.StreamWriter(tempFilename, false))
                {
                    foreach (var entry in from x in log.Read()
                             orderby x.Timestamp ascending
                             select x)
                    {
                        if ((entry != null) && (logEntryFilterPredicate(entry)))
                        {
                            sw.WriteLine(FormatLogLine(entry, writeLogEntriesUsingLocalTime));
                            ++archivedLogs;
                        }
                    }
                }
            }
            if (archivedLogs > 0)
            {
                // copy temp file into FileStore
                fileStore.Add(filename, tempFilename, DateTime.UtcNow, 0);
                if (truncateLogArchive)
                {
                    TruncateArchiveFileStore(fileStore, truncateLogArchiveMaximumFiles, truncateLogArchivePercentageToRemove);
                }
                fileStore.Save(true);
            }
            // delete temp file
            System.IO.File.Delete(tempFilename);
            // return number of archived logs
            return(archivedLogs);
        }