예제 #1
0
        public static string CreateEmptyFile(this ITempFilesManager tempFiles)
        {
            string fname = tempFiles.GenerateNewName();

            File.Create(fname).Close();
            return(fname);
        }
        public static bool?TestParsing(
            string sampleLog,
            IAlertPopup alerts,
            ITempFilesManager tempFilesManager,
            IObjectFactory objectsFactory,
            XmlNode formatRoot,
            string formatSpecificNodeName
            )
        {
            if (sampleLog == "")
            {
                alerts.ShowPopup("", "Provide sample log first", AlertFlags.Ok | AlertFlags.WarningIcon);
                return(null);
            }

            string tmpLog = tempFilesManager.GenerateNewName();

            try
            {
                XDocument clonedFormatXmlDocument = XDocument.Parse(formatRoot.OuterXml);

                UserDefinedFactoryParams createParams;
                createParams.Entry              = null;
                createParams.RootNode           = clonedFormatXmlDocument.Element("format");
                createParams.FormatSpecificNode = createParams.RootNode.Element(formatSpecificNodeName);
                createParams.FactoryRegistry    = null;
                createParams.TempFilesManager   = tempFilesManager;

                // Temporary sample file is always written in Unicode wo BOM: we don't test encoding detection,
                // we test regexps correctness.
                using (var w = new StreamWriter(tmpLog, false, new UnicodeEncoding(false, false)))
                    w.Write(sampleLog);
                ChangeEncodingToUnicode(createParams);

                var cp = ConnectionParamsUtils.CreateFileBasedConnectionParamsFromFileName(tmpLog);

                ILogProviderFactory f;
                if (formatSpecificNodeName == "regular-grammar")
                {
                    f = new RegularGrammar.UserDefinedFormatFactory(createParams);
                }
                else if (formatSpecificNodeName == "xml")
                {
                    f = new XmlFormat.UserDefinedFormatFactory(createParams);
                }
                else
                {
                    return(null);
                }
                using (f as IDisposable)
                    using (var interaction = objectsFactory.CreateTestDialog())
                    {
                        return(interaction.ShowDialog(f, cp));
                    }
            }
            finally
            {
                File.Delete(tmpLog);
            }
        }
        void ShowTextInTextViewer(string text)
        {
            var tempFileName = tempFilesManager.GenerateNewName() + ".txt";

            using (var w = new StreamWriter(tempFileName))
                w.Write(text);
            shellOpen.OpenInTextEditor(tempFileName);
        }
예제 #4
0
        static string GetTempInstallationDir(string installationDir, ITempFilesManager tempFiles)
        {
            string tempInstallationDir = Path.Combine(
                tempFiles.GenerateNewName(),
                "pending-logjoint-update");

            return(tempInstallationDir);
        }
        async void IViewControlHandler.ExecuteAction(string actionId, ClickFlags flags)
        {
            switch (actionId)
            {
            case "show":
                if (lazyOutputForm != null)
                {
                    lazyOutputForm().Show();
                }
                break;

            case "action":
                if (!await this.postprocessorsManager.RunPostprocessors(postprocessorsManager.GetPostprocessorOutputsByPostprocessorId(postprocessorKind)))
                {
                    return;
                }
                if (lazyOutputForm != null)
                {
                    var outputs = postprocessorsManager.GetPostprocessorOutputsByPostprocessorId(postprocessorKind);
                    if (outputs.Any(x => x.OutputStatus == LogSourcePostprocessorOutput.Status.Finished))
                    {
                        lazyOutputForm().Show();
                    }
                }
                break;

            case "report":
            {
                var outputs   = postprocessorsManager.GetPostprocessorOutputsByPostprocessorId(postprocessorKind);
                var summaries =
                    outputs
                    .Select(output => output.LastRunSummary)
                    .Where(summary => summary != null)
                    .OrderBy(summary => summary.HasErrors ? 0 : summary.HasWarnings ? 1 : 2);
                var text = new StringBuilder();
                foreach (var summary in summaries)
                {
                    text.Append(summary.Report ?? "");
                    text.AppendLine();
                    text.AppendLine();
                }
                if (text.Length > 0)
                {
                    var fname = Path.ChangeExtension(tempFiles.GenerateNewName(), ".txt");
                    File.WriteAllText(fname, text.ToString());
                    shellOpen.OpenInTextEditor(fname);
                }
            }
            break;
            }
        }
예제 #6
0
        private async Task LoadArchivedStorageEntries(string entriesArchiveUrl, CancellationToken cancellation)
        {
            var entriesArchiveFileName = tempFilesManager.GenerateNewName();

            using (var entriesArchiveStream = new FileStream(entriesArchiveFileName, FileMode.CreateNew))
            {
                await backendAccess.GetEntriesArchive(entriesArchiveUrl, entriesArchiveStream, cancellation);
            }
            var sectionContentTempFileName = tempFilesManager.GenerateNewName();
            var entries = new Dictionary <string, IStorageEntry>();

            using (var zipFile = new ZipFile(entriesArchiveFileName))
            {
                foreach (var zipEntry in zipFile.OfType <ZipEntry>().Where(e => e != null))
                {
                    if (zipEntry.IsDirectory)
                    {
                        continue;
                    }
                    var storageEntryId = Path.GetDirectoryName(zipEntry.Name);
                    var sectionId      = Path.GetFileName(zipEntry.Name);
                    using (var sectionContentStream = new FileStream(sectionContentTempFileName,
                                                                     FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose))
                    {
                        using (var inputStream = zipFile.GetInputStream(zipEntry))
                            IOUtils.CopyStreamWithProgress(inputStream, sectionContentStream, _ => { }, CancellationToken.None);
                        sectionContentStream.Position = 0;
                        IStorageEntry storageEntry;
                        if (!entries.TryGetValue(storageEntryId, out storageEntry))
                        {
                            entries.Add(storageEntryId, storageEntry = storageManager.GetEntryById(storageEntryId));
                        }
                        await storageEntry.LoadSectionFromSnapshot(sectionId, sectionContentStream, cancellation);
                    }
                }
            }
        }
예제 #7
0
        public static async Task SerializePostprocessorOutput(
            Task <ILogPartToken> logPartToken,
            ILogPartTokenFactories logPartTokenFactories,
            IEnumerableAsync <M.Event[]> events,
            Task <ISameNodeDetectionToken> sameNodeDetectionTokenTask,
            ISameNodeDetectionTokenFactories nodeDetectionTokenFactories,
            Func <object, TextLogEventTrigger> triggersConverter,
            string contentsEtagAttr,
            Func <Task <Stream> > openOutputStream,
            ITempFilesManager tempFiles,
            CancellationToken cancellation
            )
        {
            events       = events ?? new List <M.Event[]>().ToAsync();
            logPartToken = logPartToken ?? Task.FromResult <ILogPartToken>(null);
            sameNodeDetectionTokenTask = sameNodeDetectionTokenTask ?? Task.FromResult <ISameNodeDetectionToken>(null);

            var eventsTmpFile = tempFiles.GenerateNewName();

            Func <Task <Stream> > openTempFile(string fileName) => () => Task.FromResult <Stream>(new FileStream(fileName, FileMode.OpenOrCreate));

            var serializeMessagingEvents = events.SerializePostprocessorOutput <M.Event, M.EventsSerializer, M.IEventsVisitor>(
                triggerSerializer => new M.EventsSerializer(triggerSerializer),
                null, logPartTokenFactories, triggersConverter, null, messagingEventsElementName, openTempFile(eventsTmpFile), tempFiles, cancellation
                );

            await Task.WhenAll(serializeMessagingEvents, logPartToken, sameNodeDetectionTokenTask);

            using (var outputWriter = XmlWriter.Create(await openOutputStream(), new XmlWriterSettings()
            {
                Indent = true, Async = true, CloseOutput = true
            }))
                using (var messagingEventsReader = XmlReader.Create(eventsTmpFile))
                {
                    outputWriter.WriteStartElement("root");

                    new PostprocessorOutputETag(contentsEtagAttr).Write(outputWriter);
                    logPartTokenFactories.SafeWriteTo(await logPartToken, outputWriter);
                    nodeDetectionTokenFactories.SafeWriteTo(await sameNodeDetectionTokenTask, outputWriter);

                    messagingEventsReader.ReadToFollowing(messagingEventsElementName);
                    await outputWriter.WriteNodeAsync(messagingEventsReader, false);

                    outputWriter.WriteEndElement();             // root
                }

            File.Delete(eventsTmpFile);
        }
예제 #8
0
 static string GetTempInstallationDir(string installationDir, ITempFilesManager tempFiles)
 {
     if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
     {
         string tempInstallationDir = Path.Combine(
             tempFiles.GenerateNewName(),
             "pending-logjoint-update");
         return(tempInstallationDir);
     }
     else
     {
         // On windows: download update to a folder next to installation dir.
         // This ensures almost 100% that temp folder and installation dir are on the same HDD partition
         // which ensures speed and success of moving the temp folder in place of installation dir.
         var    localUpdateCheckId  = Guid.NewGuid().GetHashCode();
         string tempInstallationDir = Path.GetFullPath(string.Format(@"{0}\..\pending-logjoint-update-{1:x}",
                                                                     installationDir, localUpdateCheckId));
         return(tempInstallationDir);
     }
 }
예제 #9
0
        public static async Task <IPendingUpdate> Create(
            IFactory factory,
            ITempFilesManager tempFiles,
            ITraceSourceFactory traceSourceFactory,
            MultiInstance.IInstancesCounter mutualExecutionCounter,
            IReadOnlyList <Extensibility.IPluginInfo> requiredPlugins,
            string managedAssembliesPath,
            string updateLogFileName,
            CancellationToken cancellation
            )
        {
            LJTraceSource trace = traceSourceFactory.CreateTraceSource("AutoUpdater", $"pupd-{Interlocked.Increment(ref pendingUpdateIdx)}");

            string installationDir = Path.GetFullPath(
                Path.Combine(managedAssembliesPath, Constants.installationPathRootRelativeToManagedAssembliesLocation));
            string tempInstallationDir = GetTempInstallationDir(installationDir, tempFiles);

            async Task <(string tempZipFile, DownloadUpdateResult result)> Download(IUpdateDownloader updateDownloader, string name)
            {
                var tempFileName = tempFiles.GenerateNewName();

                using (var tempFileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.Write))
                {
                    trace.Info("downloading update for '{0}' to '{1}'", name, tempFileName);
                    var downloadResult = await updateDownloader.DownloadUpdate(null, tempFileStream, cancellation);

                    cancellation.ThrowIfCancellationRequested();
                    if (downloadResult.Status == DownloadUpdateResult.StatusCode.Failure)
                    {
                        throw new Exception($"Failed to download update for {name}: {downloadResult.ErrorMessage}");
                    }
                    return(tempFileName, downloadResult);
                }
            }

            var downloadResults = await Task.WhenAll(
                new[] { Download(factory.CreateAppUpdateDownloader(), "app") }
                .Union(requiredPlugins.Select(plugin => Download(factory.CreatePluginUpdateDownloader(plugin), plugin.Name)))
                );

            void UnzipDownloadedUpdate(string zipFileName, string targetDir)
            {
                using (var fs = new FileStream(zipFileName, FileMode.Open))
                    using (var zipFile = new ZipArchive(fs, ZipArchiveMode.Read))
                    {
                        try
                        {
                            zipFile.ExtractToDirectory(targetDir);
                        }
                        catch (UnauthorizedAccessException e)
                        {
                            throw new BadInstallationDirException(e);
                        }
                    }
                cancellation.ThrowIfCancellationRequested();
            }

            trace.Info("unzipping downloaded update to {0}", tempInstallationDir);
            UnzipDownloadedUpdate(downloadResults[0].tempZipFile, tempInstallationDir);

            var newUpdateInfoPath = Path.Combine(tempInstallationDir,
                                                 Constants.managedAssembliesLocationRelativeToInstallationRoot, Constants.updateInfoFileName);

            new UpdateInfoFileContent(downloadResults[0].result.ETag, DateTime.UtcNow, null).Write(newUpdateInfoPath);

            UpdatePermissions(tempInstallationDir);

            trace.Info("starting updater");

            async Task <(Process process, string autoRestartFlagFileName)> StartUpdater()
            {
                var    tempUpdaterExePath = tempFiles.GenerateNewName() + ".lj.updater.exe";
                string updaterExePath;
                string programToStart;
                string firstArg;
                string autoRestartCommandLine;
                string autoRestartIPCKey;
                string restartFlagFileName;

                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                {
                    updaterExePath = Path.Combine(installationDir, Constants.managedAssembliesLocationRelativeToInstallationRoot, "logjoint.updater.exe");
                    var monoPath = @"/Library/Frameworks/Mono.framework/Versions/Current/bin/mono";
                    programToStart         = monoPath;
                    firstArg               = string.Format("\"{0}\" ", tempUpdaterExePath);
                    restartFlagFileName    = tempFiles.GenerateNewName() + ".autorestart";
                    autoRestartIPCKey      = restartFlagFileName;
                    autoRestartCommandLine = Path.GetFullPath(Path.Combine(installationDir, ".."));
                }
                else
                {
                    updaterExePath         = Path.Combine(installationDir, "updater", "logjoint.updater.exe");
                    programToStart         = tempUpdaterExePath;
                    firstArg               = "";
                    autoRestartIPCKey      = Constants.startAfterUpdateEventName;
                    autoRestartCommandLine = Path.Combine(installationDir, "logjoint.exe");
                    restartFlagFileName    = null;
                }

                File.Copy(updaterExePath, tempUpdaterExePath);

                trace.Info("updater executable copied to '{0}'", tempUpdaterExePath);

                trace.Info("this update's log is '{0}'", updateLogFileName);

                var updaterExeProcessParams = new ProcessStartInfo()
                {
                    UseShellExecute = false,
                    FileName        = programToStart,
                    Arguments       = string.Format("{0}\"{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\" \"{6}\"",
                                                    firstArg,
                                                    installationDir,
                                                    tempInstallationDir,
                                                    mutualExecutionCounter.MutualExecutionKey,
                                                    updateLogFileName,
                                                    autoRestartIPCKey,
                                                    autoRestartCommandLine
                                                    ),
                    WorkingDirectory = Path.GetDirectoryName(tempUpdaterExePath)
                };

                trace.Info("starting updater executable '{0}' with args '{1}'",
                           updaterExeProcessParams.FileName,
                           updaterExeProcessParams.Arguments);

                Environment.SetEnvironmentVariable("MONO_ENV_OPTIONS", "");                 // todo
                var process = Process.Start(updaterExeProcessParams);

                // wait a bit to catch and log immediate updater's failure
                for (int i = 0; i < 10 && !cancellation.IsCancellationRequested; ++i)
                {
                    if (process.HasExited && process.ExitCode != 0)
                    {
                        trace.Error("updater process exited abnormally with code {0}", process.ExitCode);
                        break;
                    }
                    await Task.Delay(100);
                }
                return(process, restartFlagFileName);
            }

            var(updater, autoRestartFlagFileName) = await StartUpdater();

            var key = factory.CreateUpdateKey(
                downloadResults[0].result.ETag,
                ImmutableDictionary.CreateRange(
                    downloadResults.Skip(1).Select(r => r.result.ETag).Zip(requiredPlugins, (etag, plugin) => new KeyValuePair <string, string>(plugin.Id, etag))
                    )
                );

            var pluginsFolder = Path.Combine(tempInstallationDir, Constants.managedAssembliesLocationRelativeToInstallationRoot, "Plugins");

            if (Directory.Exists(pluginsFolder))
            {
                Directory.Delete(pluginsFolder, true);
            }
            Directory.CreateDirectory(pluginsFolder);


            var pluginFormats = new HashSet <string>();

            foreach (var plugin in downloadResults.Skip(1).Zip(requiredPlugins, (downloadResult, plugin) => (plugin, downloadResult)))
            {
                var pluginFolder = Path.Combine(pluginsFolder, plugin.plugin.Id);
                UnzipDownloadedUpdate(plugin.downloadResult.tempZipFile, pluginFolder);
                new UpdateInfoFileContent(plugin.downloadResult.result.ETag, plugin.downloadResult.result.LastModifiedUtc, null).Write(
                    Path.Combine(pluginFolder, Constants.updateInfoFileName));

                try
                {
                    Extensibility.IPluginManifest manifest = new Extensibility.PluginManifest(pluginFolder);
                    pluginFormats.UnionWith(manifest.Files
                                            .Where(f => f.Type == Extensibility.PluginFileType.FormatDefinition)
                                            .Select(f => Path.GetFileName(f.AbsolulePath).ToLower()));
                }
                catch (Extensibility.BadManifestException)
                {
                    continue;
                }
            }

            CopyCustomFormats(
                managedAssembliesPath,
                Path.Combine(tempInstallationDir, Constants.managedAssembliesLocationRelativeToInstallationRoot),
                pluginFormats,                 // Temporary measure: plugin formats used to be copied to root Formats folder. Ignore them on update.
                trace
                );

            return(new PendingUpdate(tempInstallationDir, key, trace, updater, autoRestartFlagFileName));
        }
예제 #10
0
        private async Task StartUpdater(string installationDir, string tempInstallationDir, ITempFilesManager tempFiles,
                                        MultiInstance.IInstancesCounter mutualExecutionCounter, CancellationToken cancel)
        {
            var    tempUpdaterExePath = tempFiles.GenerateNewName() + ".lj.updater.exe";
            string updaterExePath;
            string programToStart;
            string firstArg;
            string autoRestartCommandLine;
            string autoRestartIPCKey;

#if MONOMAC
            updaterExePath = Path.Combine(installationDir, managedAssembliesLocationRelativeToInstallationRoot, "logjoint.updater.exe");
            var monoPath = @"/Library/Frameworks/Mono.framework/Versions/Current/bin/mono";
            programToStart         = monoPath;
            firstArg               = string.Format("\"{0}\" ", tempUpdaterExePath);
            autoRestartIPCKey      = autoRestartFlagFileName = tempFiles.GenerateNewName() + ".autorestart";
            autoRestartCommandLine = Path.GetFullPath(Path.Combine(installationDir, ".."));
#else
            updaterExePath         = Path.Combine(installationDir, "updater", "logjoint.updater.exe");
            programToStart         = tempUpdaterExePath;
            firstArg               = "";
            autoRestartIPCKey      = startAfterUpdateEventName;
            autoRestartCommandLine = Path.Combine(installationDir, "logjoint.exe");
#endif

            File.Copy(updaterExePath, tempUpdaterExePath);

            trace.Info("updater executable copied to '{0}'", tempUpdaterExePath);

            var updateLogFileName = ComposeUpdateLogFileName();
            trace.Info("this update's log is '{0}'", updateLogFileName);

            var updaterExeProcessParams = new ProcessStartInfo()
            {
                UseShellExecute = false,
                FileName        = programToStart,
                Arguments       = string.Format("{0}\"{1}\" \"{2}\" \"{3}\" \"{4}\" \"{5}\" \"{6}\"",
                                                firstArg,
                                                installationDir,
                                                tempInstallationDir,
                                                mutualExecutionCounter.MutualExecutionKey,
                                                updateLogFileName,
                                                autoRestartIPCKey,
                                                autoRestartCommandLine
                                                ),
                WorkingDirectory = Path.GetDirectoryName(tempUpdaterExePath)
            };

            trace.Info("starting updater executable '{0}' with args '{1}'",
                       updaterExeProcessParams.FileName,
                       updaterExeProcessParams.Arguments);

            Environment.SetEnvironmentVariable("MONO_ENV_OPTIONS", "");             // todo
            using (var process = Process.Start(updaterExeProcessParams))
            {
                // wait a bit to catch and log immediate updater's failure
                for (int i = 0; i < 10 && !cancel.IsCancellationRequested; ++i)
                {
                    if (process.HasExited && process.ExitCode != 0)
                    {
                        trace.Error("updater process exited abnormally with code {0}", process.ExitCode);
                        break;
                    }
                    await Task.Delay(100);
                }
            }
        }
        public static async Task SerializePostprocessorOutput <Evt, Serializer, EvtVisitor>(
            this IEnumerableAsync <Evt[]> events,
            Func <Action <object, XElement>, Serializer> serializerFactory,
            Task <ILogPartToken> rotatedLogPartToken,
            ILogPartTokenFactories rotatedLogPartFactories,
            Func <object, TextLogEventTrigger> triggersConverter,
            string contentsEtagAttr,
            string rootElementName,
            string outputFileName,
            ITempFilesManager tempFiles,
            CancellationToken cancellation
            ) where Evt : IVisitable <EvtVisitor> where Serializer : class, IEventsSerializer, EvtVisitor
        {
            rotatedLogPartToken = rotatedLogPartToken ?? Task.FromResult <ILogPartToken>(null);
            var        sortKeyAttr     = XName.Get("__key");
            var        chunks          = new List <string>();
            Serializer serializer      = null;
            Action     resetSerializer = () =>
            {
                if (serializer?.Output?.Count > 0)
                {
                    string chunkFileName = tempFiles.GenerateNewName();
                    chunks.Add(chunkFileName);
                    using (var writer = XmlWriter.Create(chunkFileName, new XmlWriterSettings()
                    {
                        OmitXmlDeclaration = true,
                        ConformanceLevel = ConformanceLevel.Fragment
                    }))
                    {
                        foreach (var e in serializer.Output.OrderBy(e => e.Attribute(sortKeyAttr).Value))
                        {
                            e.WriteTo(writer);
                        }
                    }
                }
                serializer = serializerFactory((trigger, elt) =>
                {
                    triggersConverter(trigger).Save(elt);
                    elt.SetAttributeValue(sortKeyAttr, ((IOrderedTrigger)trigger).Index.ToString("x8"));
                });
            };

            resetSerializer();
            await events.ForEach(batch =>
            {
                foreach (var e in batch)
                {
                    e.Visit(serializer);
                    if (serializer.Output.Count >= 8 * 1024)
                    {
                        resetSerializer();
                    }
                }
                return(Task.FromResult(!cancellation.IsCancellationRequested));
            });

            resetSerializer();

            if (cancellation.IsCancellationRequested)
            {
                return;
            }

            using (var outputWriter = XmlWriter.Create(outputFileName, new XmlWriterSettings()
            {
                Indent = true
            }))
            {
                outputWriter.WriteStartElement(rootElementName);
                new PostprocessorOutputETag(contentsEtagAttr).Write(outputWriter);
                rotatedLogPartFactories.SafeWriteTo(await rotatedLogPartToken, outputWriter);
                var readersSettings = new XmlReaderSettings()
                {
                    ConformanceLevel = ConformanceLevel.Fragment
                };
                var readers = chunks.Select(chunkFileName => XmlReader.Create(chunkFileName, readersSettings)).ToList();
                try
                {
                    var q = new VCSKicksCollection.PriorityQueue <KeyValuePair <XmlReader, XElement> >(Comparer <KeyValuePair <XmlReader, XElement> > .Create((item1, item2) =>
                    {
                        return(string.CompareOrdinal(item1.Value.Attribute(sortKeyAttr).Value, item2.Value.Attribute(sortKeyAttr).Value));
                    }));
                    Action <XmlReader> enqueueReader = reader =>
                    {
                        if (!reader.EOF)
                        {
                            if (reader.MoveToContent() != XmlNodeType.Element)
                            {
                                throw new InvalidOperationException("bad chunk");
                            }
                            q.Enqueue(new KeyValuePair <XmlReader, XElement>(reader, (XElement)XNode.ReadFrom(reader)));
                        }
                    };
                    readers.ForEach(enqueueReader);
                    while (q.Count > 0)
                    {
                        var item = q.Dequeue();
                        item.Value.Attribute(sortKeyAttr).Remove();
                        item.Value.WriteTo(outputWriter);
                        enqueueReader(item.Key);
                    }
                }
                finally
                {
                    readers.ForEach(r => r.Dispose());
                    chunks.ForEach(chunkFileName => tempFiles.DeleteIfTemporary(chunkFileName));
                }
                outputWriter.WriteEndElement();                 // end of root node
            }
        }
예제 #12
0
        public static async Task SerializePostprocessorOutput(
            IEnumerableAsync <M.Event[]> events,
            IEnumerableAsync <TLBlock.Event[]> timelineComments,
            IEnumerableAsync <SIBlock.Event[]> stateInspectorComments,
            Task <ILogPartToken> logPartToken,
            ILogPartTokenFactories logPartTokenFactories,
            Func <object, TextLogEventTrigger> triggersConverter,
            string contentsEtagAttr,
            string outputFileName,
            ITempFilesManager tempFiles,
            CancellationToken cancellation
            )
        {
            events                 = events ?? new List <M.Event[]>().ToAsync();
            timelineComments       = timelineComments ?? new List <TLBlock.Event[]>().ToAsync();
            stateInspectorComments = stateInspectorComments ?? new List <SIBlock.Event[]>().ToAsync();
            logPartToken           = logPartToken ?? Task.FromResult <ILogPartToken>(null);

            var eventsTmpFile                = tempFiles.GenerateNewName();
            var timelineCommentsTmpFile      = tempFiles.GenerateNewName();
            var stateInsectorCommentsTmpFile = tempFiles.GenerateNewName();

            var serializeMessagingEvents = events.SerializePostprocessorOutput <M.Event, M.EventsSerializer, M.IEventsVisitor>(
                triggerSerializer => new M.EventsSerializer(triggerSerializer),
                null, logPartTokenFactories, triggersConverter, null, messagingEventsElementName, eventsTmpFile, tempFiles, cancellation
                );

            var serializeTimelineComments = timelineComments.SerializePostprocessorOutput <TLBlock.Event, TLBlock.EventsSerializer, TLBlock.IEventsVisitor>(
                triggerSerializer => new TLBlock.EventsSerializer(triggerSerializer),
                null, logPartTokenFactories, triggersConverter, null, timelineCommentsElementName, timelineCommentsTmpFile, tempFiles, cancellation
                );

            var serializeStateInspectorComments = stateInspectorComments.SerializePostprocessorOutput <SIBlock.Event, SIBlock.EventsSerializer, SIBlock.IEventsVisitor>(
                triggerSerializer => new SIBlock.EventsSerializer(triggerSerializer),
                null, logPartTokenFactories, triggersConverter, null, stateCommentsElementName, stateInsectorCommentsTmpFile, tempFiles, cancellation
                );

            await Task.WhenAll(serializeMessagingEvents, serializeTimelineComments, serializeStateInspectorComments, logPartToken);

            using (var outputWriter = XmlWriter.Create(outputFileName, new XmlWriterSettings()
            {
                Indent = true, Async = true
            }))
                using (var messagingEventsReader = XmlReader.Create(eventsTmpFile))
                    using (var timelineCommentsReader = XmlReader.Create(timelineCommentsTmpFile))
                        using (var stateInspectorCommentsReader = XmlReader.Create(stateInsectorCommentsTmpFile))
                        {
                            outputWriter.WriteStartElement("root");

                            new PostprocessorOutputETag(contentsEtagAttr).Write(outputWriter);
                            logPartTokenFactories.SafeWriteTo(await logPartToken, outputWriter);

                            messagingEventsReader.ReadToFollowing(messagingEventsElementName);
                            await outputWriter.WriteNodeAsync(messagingEventsReader, false);

                            timelineCommentsReader.ReadToFollowing(timelineCommentsElementName);
                            await outputWriter.WriteNodeAsync(timelineCommentsReader, false);

                            stateInspectorCommentsReader.ReadToFollowing(stateCommentsElementName);
                            await outputWriter.WriteNodeAsync(stateInspectorCommentsReader, false);

                            outputWriter.WriteEndElement();     // root
                        }

            File.Delete(eventsTmpFile);
            File.Delete(timelineCommentsTmpFile);
            File.Delete(stateInsectorCommentsTmpFile);
        }