[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=CRLF\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"nuget\"\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.sln.docstates\n\n# Build results\nInstalls/\n[Dd]ebug/\n[Rr]elease/\nx64/\nbuild/\n[Bb]in/\n[Oo]bj/\n.vs/\n\n# Squirrel releases\nReleases/\n*.nupkg\n*.nuspec\n\n# Enable \"build/\" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets\n!packages/*/build/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n*_i.c\n*_p.c\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.log\n*.scc\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n*.ncrunch*\n.*crunch*.local.xml\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.Publish.xml\n\n# NuGet Packages Directory\n## TODO: If you have NuGet Package Restore enabled, uncomment the next line\n#packages/\n\n# Windows Azure Build Output\ncsx\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Others\nsql/\n*.Cache\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.[Pp]ublish.xml\n*.pfx\n*.publishsettings\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file to a newer\n# Visual Studio version. Backup files are not needed, because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\nApp_Data/*.mdf\nApp_Data/*.ldf\n\n\n#LightSwitch generated files\nGeneratedArtifacts/\n_Pvt_Extensions/\nModelManifest.xml\n\n# =========================\n# Windows detritus\n# =========================\n\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Mac desktop service store files\n.DS_Store\n\n# Nuget\npackages\n/.vs\n/.idea\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Releases are made from the master branch, but new developments are done on the develop branch.  There are a lot of new developments on that branch, so please be aware as changes to master directly may not merge nicely.\n"
  },
  {
    "path": "CustomDictionary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Dictionary>\n  <Words>\n    <Recognized>\n      <Word>Colour</Word>\n      <Word>Colours</Word>\n      <Word>Initialise</Word>\n      <Word>Initialisation</Word>\n      <Word>Appender</Word>\n      <Word>Ms</Word>\n      <Word>Gui</Word>\n      <Word>Mvvm</Word>\n    </Recognized>\n    <!--<Deprecated>\n      <Term PreferredAlternate=\"ModelViewViewModel\">Mvvm</Term>\n    </Deprecated>-->\n  </Words>\n  <Acronyms>\n    <CasingExceptions>\n    </CasingExceptions>\n  </Acronyms>\n\n</Dictionary>"
  },
  {
    "path": "GitVersion.yml",
    "content": "mode: Mainline\nbranches: {}\nignore:\n  sha: []\nmerge-message-formats: {}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# Microsoft Public License (Ms-PL)\n\nMicrosoft Public License (Ms-PL)\n\nThis license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.\n\n1. Definitions\n\nThe terms \"reproduce,\" \"reproduction,\" \"derivative works,\" and \"distribution\" have the same meaning here as under U.S. copyright law.\n\nA \"contribution\" is the original software, or any additions or changes to the software.\n\nA \"contributor\" is any person that distributes its contribution under this license.\n\n\"Licensed patents\" are a contributor's patent claims that read directly on its contribution.\n\n2. Grant of Rights\n\n(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.\n\n(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.\n\n3. Conditions and Limitations\n\n(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.\n\n(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.\n\n(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.\n\n(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.\n\n(E) The software is licensed \"as-is.\" You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.\n"
  },
  {
    "path": "README.md",
    "content": "# ![](docs/Debug.png) sentinel\nLog-viewer with filtering and highlighting\n\n![](docs/Home_sentinel-0.11.0.0.png)\n\n## Log Sources \nSentinel is a viewer for log-files - specifically I designed it to act as a network end-point for the likes of nLog and log4net, additionally it then works really well for capturing log entries from multiple sources.  \n\nSource                | Status\n--------------------- | ------\nLog4Net UdpAppender   | Supported \nnLog's nLogViewer     | Supported\nTrace Listener        | Planned\nLog-File Watcher      | Experimental\nCustom, via plug-in   | Planned\nMSBuild               | Plug-in in source-repo\n\n## Command-Line usage\nThere are command line options that allow control over Sentinel when started, options available include the following:\n* Loading of a saved Session File\n* nLog network listener\n* log4net network listener\n\n## Command line options\nIf no command line options are specified, the standard New Session wizard will launch at start-up.\n\n### Launch with NLog listener enabled\n```\nsentinel nlog [--port <port-number>] [--tcp]\n```\nDefaults to port 9999 and Udp if not specified.\n\n### Launch with Log4Net listener enabled\n```\nsentinel log4net [--port <port-number>]\n```\nDefaults to port 9998 if not specified.\n\n### Launch with previously saved session file\n```\nsentinel filename.SNTL\n```\n\n## nLog's NLogViewer target configuration\nTo allow a nLog based application transmit its log messages to Sentinel, use a configuration like the one shown below:\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<nlog xmlns=\"http://www.nlog-project.org/schemas/NLog.xsd\"\n      xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <targets>\n    <target xsi:type=\"NLogViewer\"\n            name=\"viewer\"\n            address=\"udp://127.0.0.1:9999\"/>\n  </targets>\n  <rules>\n    <logger name=\"*\"\n            minlevel=\"Debug\"\n            writeTo=\"viewer\" />\n  </rules>\n</nlog>\n```\n\n### Showing nLog debug information (0.12.0.0 onwards)\nIf the above configuration is adjusted to enable {{includeSourceInfo}} then it is possible to see the file, class, method and line number corresponding to where the message is emitted.  Some of this information is only reported if the source program is compiled in DEBUG mode (e.g. RELEASE mode strips this information)\n```xml\n<target name=\"viewer\"\n        xsi:type=\"NLogViewer\"\n        includeSourceInfo=\"true\"\n        address=\"udp://127.0.0.1:9999\" />\n```\n\n## Log4Net UdpAppender configuration\nTo allow a log4net application transmit its log messages to Sentinel, use a configuration like the one shown below:\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n  <configSections>\n    <section name=\"log4net\"\n             type=\"log4net.Config.Log4NetConfigurationSectionHandler, log4net\"/>\n  </configSections>\n\n  <log4net>\n    <appender name=\"udp\"\n              type=\"log4net.Appender.UdpAppender\">\n\n      <RemoteAddress value=\"127.0.0.2\"/>\n      <RemotePort value=\"9999\"/>\n      <layout type=\"log4net.Layout.XmlLayout\"/>\n      <encoding value=\"utf-8\" />\n    </appender>\n\n    <root>\n      <appender-ref ref=\"udp\"/>\n    </root>\n  </log4net>\n</configuration>\n```\n### Showing log4net debug information (0.12.1.0 onwards)\nIf the above configuration is adjusted to enable {{locationInfo}} then it is possible to see the file, class, method and line number corresponding to where the message is emitted.  Some of this information is only reported if the source program is compiled in DEBUG mode (e.g. RELEASE mode strips this information)\n```xml\n<layout type=\"log4net.Layout.XmlLayout\">\n    <locationInfo value=\"true\" />\n</layout>\n```\n\n## Log Entries\nLog file entries map the the following interface.  This is core record for a log-file entry and used to populate the columns within the live-log view.  Note, by using [Classifiers](#Classifiers) it is possible to rearrange and/or change the content of these fields upon receiving a new log-entry.\n\n```c#\npublic interface ILogEntry\n{\n    string Classification { get; set; }\n    DateTime DateTime { get; set; }\n    string Description { get; set; }\n    string Source { get; set; }\n    string System { get; set; }\n    string Thread { get; set; }\n    string Type { get; set; }\n}\n```\n\nLog entries may be classified, highlighted and filtered based upon special services:\n* Classifiers can change the properties of a log entry\n* Highlighters can change its appearance.\n* Filters can be used to suppress the displaying of matching entries.\n\n## Classifiers\nUpon receiving a new log-entry it is processed through registered classifiers.  Classifiers have the ability to rewrite the log-entry prior to passing it to the visualisation aspect of Sentinel. \n\nAs an example, suppose the incoming message when starting with the phrase \"Sleeping for another\" is intended to be switched from its supplied type (e.g. DEBUG or INFO) to its own type **Timing**.  The ClassifierViewModel registers this on construction.  Note that the regular expression below also rewrites the Description to the named-capture _description_, effectively stripping off the prefix \"Sleeping for another\" - information no longer needed due to the reclassification.\n\n```c#\nitems.Add(\n\tnew DescriptionTypeClassifier(\"Timing\", \"Timing\")\n\t\t{\n\t\t\tEnabled = true,\n\t\t\tName = \"Timing messages\",\n\t\t\tRegexString = @\"^Sleeping for another (?<description>[^$](^$)+)$\"\n\t\t});\n```\n\nIn addition to reclassifying messages, the Classifier mechanism is currently used to make other changes to the message appearance.  Continuing the example above, using a {{TypeImageClassifier}} it is possible to specify the image to be used for entries with a type of **Timing**.\n\n```c#\nitems.Add(\n\tnew TypeImageClassifier(\"Timing\", \"/Resources/Clock.png\")\n\t\t{\n\t\t\tEnabled = true,\n\t\t\tName = \"Timing Image\",\n\t\t});\n```\nThe classifiers can be seen in the Preferences dialog-box.\n\n![Preferences - Classifiers](docs/Home_Preferences-Classifiers.png)\n## Highlighters\nCustomisable and extendible highlighters that may be toggled on and off during live preview.  The current implementation limits pattern matching to the Type and System fields, although this will be extended to all fields.  An example of why this is useful, in the classifiers section above, a new type of \"Timing\" was added, this type can get its own highlighting style.  \n\n![Preferences - Highlighters](docs/Home_Preferences-Highlighters.png)\n\nHighlighters can match the contents of the Type and System fields\n* Exact Strings\n* Substrings\n* Regular Expressions\n\n![Adding Regex Highlighter](docs/Home_Add%20Highlighter%20-%20Regex.png)\n\nIf the matching field is set to **Type** an the match string specified as \"Timing\", a new highlighter for the timing can be added.  User-defined highlighters are automatically added to the toolbar for ease of enabling and disabling of the highlighting.  \n\n![Toolbar - User defined highlighters](docs/Home_Toolbar%20-%20User%20defined%20highlighter.png)\n\nThe highlighters work on a first-come, wins principle.  Therefore, the order of the entries in the highlighters section of the Preferences dialog-box are important.  It is possible to hide the highlighting of  FATAL messages if a highlighter is positioned before FATAL and gets a match.\n\n## Filters\nFilters are very much like highlighters except their purpose is to remove log-entries from the displayed values (note, the values are not lost, just hidden).  Filters may be toggled on and off during the session.  Filters are evaluated in the order specified, but since filters works on an any-match = hide principle the evaluation stops on the first match, resulting on the entry being hidden.  Note, this means that messages displayed have travelled through ALL of the filters without being matched, this isn't a cost free aspect, so be careful about how many enabled filters you have!\n\n## Extractors\nExtractors are the inverse of Filters, log entries must match the extractor to be visible.  This can be very useful if you, for example, define an extractor for a specific logging condition and want to quickly see if and how often it happened.\n"
  },
  {
    "path": "Sentinel/.vscode/launch.json",
    "content": "{\n   // Use IntelliSense to find out which attributes exist for C# debugging\n   // Use hover for the description of the existing attributes\n   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md\n   \"version\": \"0.2.0\",\n   \"configurations\": [\n        {\n            \"name\": \".NET Core Launch (console)\",\n            \"type\": \"coreclr\",\n            \"request\": \"launch\",\n            \"preLaunchTask\": \"build\",\n            // If you have changed target frameworks, make sure to update the program path.\n            \"program\": \"${workspaceFolder}/bin/Debug/net5.0/Sentinel.dll\",\n            \"args\": [],\n            \"cwd\": \"${workspaceFolder}\",\n            // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console\n            \"console\": \"internalConsole\",\n            \"stopAtEntry\": false\n        },\n        {\n            \"name\": \".NET Core Attach\",\n            \"type\": \"coreclr\",\n            \"request\": \"attach\",\n            \"processId\": \"${command:pickProcess}\"\n        }\n    ]\n}"
  },
  {
    "path": "Sentinel/.vscode/tasks.json",
    "content": "{\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"label\": \"build\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"build\",\n                \"${workspaceFolder}/Sentinel.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        },\n        {\n            \"label\": \"publish\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"publish\",\n                \"${workspaceFolder}/Sentinel.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        },\n        {\n            \"label\": \"watch\",\n            \"command\": \"dotnet\",\n            \"type\": \"process\",\n            \"args\": [\n                \"watch\",\n                \"run\",\n                \"${workspaceFolder}/Sentinel.csproj\",\n                \"/property:GenerateFullPaths=true\",\n                \"/consoleloggerparameters:NoSummary\"\n            ],\n            \"problemMatcher\": \"$msCompile\"\n        }\n    ]\n}"
  },
  {
    "path": "Sentinel/Classification/Classifier.cs",
    "content": "﻿namespace Sentinel.Classification\n{\n    using System.Diagnostics;\n    using System.Runtime.Serialization;\n    using System.Text.RegularExpressions;\n\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class Classifier : ViewModelBase, IClassifier\n    {\n        private bool enabled = true;\n\n        private LogEntryFields field;\n\n        private MatchMode mode;\n\n        private string name;\n\n        private string type;\n\n        private string pattern;\n\n        private Regex regex;\n\n        public Classifier()\n        {\n            PropertyChanged += (sender, e) =>\n                {\n                    if (e.PropertyName == \"Field\" || e.PropertyName == \"Mode\" || e.PropertyName == \"Pattern\")\n                    {\n                        if (Mode == MatchMode.RegularExpression && Pattern != null)\n                        {\n                            regex = new Regex(Pattern);\n                        }\n\n                        OnPropertyChanged(nameof(Description));\n                    }\n                };\n        }\n\n        public Classifier(string name, bool enabled, LogEntryFields field, MatchMode mode, string pattern, string type)\n        {\n            Name = name;\n            Enabled = enabled;\n            Field = field;\n            Mode = mode;\n            Pattern = pattern;\n            Type = type;\n            regex = new Regex(pattern);\n\n            PropertyChanged += (sender, e) =>\n                {\n                    if (e.PropertyName == \"Field\" || e.PropertyName == \"Mode\" || e.PropertyName == \"Pattern\")\n                    {\n                        if (Mode == MatchMode.RegularExpression && Pattern != null)\n                        {\n                            regex = new Regex(Pattern);\n                        }\n\n                        OnPropertyChanged(nameof(Description));\n                    }\n                };\n        }\n\n        public string Description\n        {\n            get\n            {\n                var modeDescription = \"Exact\";\n\n                switch (Mode)\n                {\n                    case MatchMode.RegularExpression:\n                        modeDescription = \"RegEx\";\n                        break;\n                    case MatchMode.CaseSensitive:\n                        modeDescription = \"Case sensitive\";\n                        break;\n                    case MatchMode.CaseInsensitive:\n                        modeDescription = \"Case insensitive\";\n                        break;\n                }\n\n                return $\"{modeDescription} match of {Pattern} in the {Field} field\";\n            }\n        }\n\n        public bool Enabled\n        {\n            get\n            {\n                return enabled;\n            }\n\n            set\n            {\n                if (enabled != value)\n                {\n                    enabled = value;\n                    OnPropertyChanged(nameof(Enabled));\n                }\n            }\n        }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                field = value;\n                OnPropertyChanged(nameof(Field));\n            }\n        }\n\n        public string HighlighterType => \"Basic Highlighter\";\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                if (mode != value)\n                {\n                    mode = value;\n                    OnPropertyChanged(nameof(Mode));\n                }\n            }\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (pattern != value)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public string Type\n        {\n            get\n            {\n                return type;\n            }\n\n            set\n            {\n                if (type != value)\n                {\n                    type = value;\n                    OnPropertyChanged(nameof(Type));\n                }\n            }\n        }\n\n        public ILogEntry Classify(ILogEntry logEntry)\n        {\n            Debug.Assert(logEntry != null, \"logEntry can not be null.\");\n\n            if (IsMatch(logEntry))\n            {\n                logEntry.MetaData[\"Classification\"] = Type;\n                logEntry.Type = Type;\n            }\n\n            return logEntry;\n        }\n\n        public bool IsMatch(ILogEntry logEntry)\n        {\n            Debug.Assert(logEntry != null, \"logEntry can not be null.\");\n\n            if (string.IsNullOrWhiteSpace(Pattern))\n            {\n                return false;\n            }\n\n            string target;\n\n            switch (Field)\n            {\n                case LogEntryFields.None:\n                    target = string.Empty;\n                    break;\n                case LogEntryFields.Type:\n                    target = logEntry.Type;\n                    break;\n                case LogEntryFields.System:\n                    target = logEntry.System;\n                    break;\n                case LogEntryFields.Classification:\n                    target = string.Empty;\n                    break;\n                case LogEntryFields.Thread:\n                    target = logEntry.Thread;\n                    break;\n                case LogEntryFields.Source:\n                    target = logEntry.Source;\n                    break;\n                case LogEntryFields.Description:\n                    target = logEntry.Description;\n                    break;\n                case LogEntryFields.Host:\n                    target = string.Empty;\n                    break;\n                default:\n                    target = string.Empty;\n                    break;\n            }\n\n            switch (Mode)\n            {\n                case MatchMode.Exact:\n                    return target.Equals(Pattern);\n                case MatchMode.CaseSensitive:\n                    return target.Contains(Pattern);\n                case MatchMode.CaseInsensitive:\n                    return target.ToLower().Contains(Pattern.ToLower());\n                case MatchMode.RegularExpression:\n                    return regex != null && regex.IsMatch(target);\n            }\n\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/ClassifyingService.cs",
    "content": "﻿namespace Sentinel.Classification\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n\n    using Sentinel.Classification.Gui;\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// View Model for classifier collection.  This has been written to operate\n    /// as a ServiceLocator provided resource, so there is only one collection\n    /// across the whole of the system.\n    /// </summary>\n    /// <typeparam name=\"T\">The first generic type parameter.</typeparam>\n    [DataContract]\n    public class ClassifyingService<T> : ViewModelBase, IClassifyingService<T>, IDefaultInitialisation\n        where T : class, IClassifier\n    {\n        private readonly CollectionChangeHelper<T> collectionHelper = new CollectionChangeHelper<T>();\n\n        private readonly IAddClassifyingService addClassifyingService = new AddClassifier();\n\n        private readonly IEditClassifyingService editClassifyingService = new EditClassifier();\n\n        private readonly IRemoveClassifyingService removeClassifyingService = new RemoveClassifier();\n\n        private int selectedIndex = -1;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"ClassifyingService{T}\"/> class.\n        /// </summary>\n        public ClassifyingService()\n        {\n            Add = new DelegateCommand(AddClassifier);\n            Edit = new DelegateCommand(EditClassifier, e => SelectedIndex != -1);\n            Remove = new DelegateCommand(RemoveClassifier, e => SelectedIndex != -1);\n            OrderEarlier = new DelegateCommand(MoveItemUp, e => SelectedIndex > 0);\n            OrderLater = new DelegateCommand(\n                MoveItemDown,\n                e => SelectedIndex < (Classifiers.Count - 1) && SelectedIndex != -1);\n\n            Classifiers = new ObservableCollection<T>();\n\n            // Register self as an observer of the collection.\n            collectionHelper.ManagerName = \"Classifiers\";\n            collectionHelper.OnPropertyChanged += CustomClassifierPropertyChanged;\n            collectionHelper.NameLookup += e => e.Name;\n            Classifiers.CollectionChanged += collectionHelper.AttachDetach;\n        }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the add-classifier functionality.\n        /// </summary>\n        public ICommand Add { get; private set; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the edit-classifier functionality.\n        /// </summary>\n        public ICommand Edit { get; private set; }\n\n        /// <summary>\n        /// Gets or sets the <c>ObservableCollection</c> of items representing the\n        /// collection of Classifiers.\n        /// </summary>\n        public ObservableCollection<T> Classifiers { get; set; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the functionality to\n        /// move the currently selected element to an earlier position\n        /// in the ordered list of classifiers.\n        /// </summary>\n        public ICommand OrderEarlier { get; private set; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the functionality to\n        /// move the currently selected element to a later position\n        /// in the ordered list of classifiers.\n        /// </summary>\n        public ICommand OrderLater { get; private set; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the functionality to\n        /// remove the selected element from the list of classifiers.\n        /// </summary>\n        public ICommand Remove { get; private set; }\n\n        /// <summary>\n        /// Gets or sets the index for the selected item in the list\n        /// of classifiers.  The selected index is used for commands\n        /// such as <seealso cref=\"OrderEarlier\">OrderEarlier</seealso>,\n        /// <seealso cref=\"OrderLater\">OrderLater</seealso>\n        /// and <seealso cref=\"Remove\">Remove</seealso>.\n        /// </summary>\n        public int SelectedIndex\n        {\n            get => selectedIndex;\n\n            set\n            {\n                if (value != selectedIndex)\n                {\n                    selectedIndex = value;\n                    OnPropertyChanged(nameof(SelectedIndex));\n                    CommandManager.InvalidateRequerySuggested();\n                }\n            }\n        }\n\n        public void Initialise()\n        {\n            Classifiers.Add(new Classifier(\"Timing messages\", true, LogEntryFields.Description, MatchMode.RegularExpression, @\"^\\[SimulationTime\\] (?<description>[^$]+)$\", \"TIMING\") as T);\n            Classifiers.Add(new Classifier(\"Smp messages\", true, LogEntryFields.Description, MatchMode.RegularExpression, \"Src:'(?<system>[^']+)', Msg:'(?<description>.*)'$\", \"TIMING\") as T);\n            Classifiers.Add(new Classifier(\"SimSat messages\", true, LogEntryFields.Description, MatchMode.RegularExpression, \"SIMSAT:'(?<system>[^']+)', Msg:'(?<description>.*)'$\", \"TIMING\") as T);\n        }\n\n        public ILogEntry Classify(ILogEntry entry)\n        {\n            return Classifiers\n                .Where(classifier => classifier.Enabled)\n                .Aggregate(entry, (current, classifier) => classifier.Classify(current));\n        }\n\n        /// <summary>\n        /// Add a new classifier to the collection of classifiers.\n        /// <para>\n        /// Private method that implements the <c>ICommand</c> <seealso cref=\"Add\">\n        /// Add</seealso> through a <c>DelegateCommand</c>.\n        /// </para>\n        /// </summary>\n        /// <param name=\"obj\">Delegate object data - unused.</param>\n        private void AddClassifier(object obj)\n        {\n            addClassifyingService.Add();\n        }\n\n        /// <summary>\n        /// Edit the currently selected classifier (defined by the\n        /// <seealso cref=\"SelectedIndex\">SelectedIndex</seealso>\n        /// property).\n        /// <para>\n        /// Private method that implements the <c>ICommand</c> <seealso cref=\"Edit\">\n        /// Edit</seealso> through a <c>DelegateCommand</c>.\n        /// </para>\n        /// </summary>\n        /// <param name=\"obj\">Delegate object data - unused.</param>\n        private void EditClassifier(object obj)\n        {\n            var classifier = Classifiers.ElementAt(SelectedIndex);\n            if (classifier != null)\n            {\n                editClassifyingService.Edit(classifier);\n            }\n        }\n\n        /// <summary>\n        /// Move the currently selected classifier (defined by the\n        /// <seealso cref=\"SelectedIndex\">SelectedIndex</seealso>\n        /// property) to later in the ordered list.\n        /// <para>\n        /// Private method that implements the <c>ICommand</c> <seealso cref=\"OrderLater\">\n        /// OrderLater</seealso> through a <c>DelegateCommand</c>.\n        /// </para>\n        /// </summary>\n        /// <param name=\"obj\">Delegate object data - unused.</param>\n        private void MoveItemDown(object obj)\n        {\n            if (selectedIndex != -1)\n            {\n                lock (this)\n                {\n                    Debug.Assert(\n                        selectedIndex >= 0,\n                        \"SelectedIndex must be >= 0.\");\n                    Debug.Assert(\n                        selectedIndex < Classifiers.Count - 1,\n                        \"SelectedIndex must be within the index range of Items collection\");\n                    Debug.Assert(\n                        Classifiers.Count > 1,\n                        \"Can not move an item unless there is more than one.\");\n\n                    lock (Classifiers)\n                    {\n                        Classifiers.Swap(selectedIndex, selectedIndex + 1);\n                    }\n                }\n            }\n        }\n\n        /// <summary>\n        /// Move the currently selected classifier (defined by the\n        /// <seealso cref=\"SelectedIndex\">SelectedIndex</seealso>\n        /// property) to earlier in the ordered list.\n        /// <para>\n        /// Private method that implements the <c>ICommand</c> <seealso cref=\"OrderEarlier\">\n        /// OrderEarlier</seealso> through a <c>DelegateCommand</c>.\n        /// </para>\n        /// </summary>\n        /// <param name=\"obj\">Delegate object data - unused.</param>\n        private void MoveItemUp(object obj)\n        {\n            if (selectedIndex != -1)\n            {\n                lock (this)\n                {\n                    Debug.Assert(selectedIndex >= 0, \"SelectedIndex must be valid, e.g. >= 0\");\n                    Debug.Assert(Classifiers.Count > 1, \"Can only move item if more than one.\");\n\n                    lock (Classifiers)\n                    {\n                        Classifiers.Swap(selectedIndex, selectedIndex - 1);\n                    }\n                }\n            }\n        }\n\n        /// <summary>\n        /// Removes the currently selected classifier (defined by the\n        /// <seealso cref=\"SelectedIndex\">SelectedIndex</seealso>\n        /// property).\n        /// <para>\n        /// Private method that implements the <c>ICommand</c> <seealso cref=\"Remove\">\n        /// Remove</seealso> through a <c>DelegateCommand</c>.\n        /// </para>\n        /// </summary>\n        /// <param name=\"obj\">Delegate object data - unused.</param>\n        private void RemoveClassifier(object obj)\n        {\n            var classifier = Classifiers.ElementAt(SelectedIndex);\n            removeClassifyingService.Remove(classifier);\n        }\n\n        private void CustomClassifierPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            var classifier = sender as IClassifier;\n            if (classifier != null)\n            {\n                Trace.WriteLine(\n                    string.Format(\n                        \"ClassifyingService saw some activity on {0} (IsEnabled = {1})\",\n                        classifier.Name,\n                        classifier.Enabled));\n            }\n\n            OnPropertyChanged(string.Empty);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Gui/AddClassifier.cs",
    "content": "﻿namespace Sentinel.Classification.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Services;\n\n    public class AddClassifier : IAddClassifyingService\n    {\n        public void Add()\n        {\n            var classifierWindow = new AddEditClassifierWindow();\n            using (var data = new AddEditClassifier(classifierWindow, false))\n            {\n                classifierWindow.DataContext = data;\n                classifierWindow.Owner = Application.Current.MainWindow;\n\n                var dialogResult = classifierWindow.ShowDialog();\n                if (dialogResult != null && (bool)dialogResult)\n                {\n                    var classifier = Construct(data);\n                    if (classifier != null)\n                    {\n                        var service = ServiceLocator.Instance.Get<IClassifyingService<IClassifier>>();\n                        service?.Classifiers.Add(classifier);\n                    }\n                }\n            }\n        }\n\n        private static Classifier Construct(AddEditClassifier data)\n        {\n            return new Classifier\n                       {\n                           Name = data.Name,\n                           Type = data.Type,\n                           Field = data.Field,\n                           Mode = data.Mode,\n                           Pattern = data.Pattern,\n                           Enabled = true,\n                       };\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Gui/AddEditClassifier.cs",
    "content": "﻿namespace Sentinel.Classification.Gui\n{\n    using System.Windows;\n    using System.Windows.Input;\n\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    public class AddEditClassifier : ViewModelBase\n    {\n        private readonly Window window;\n\n        private string name = \"Unnamed\";\n\n        private string pattern = \"pattern\";\n\n        private LogEntryFields field;\n\n        private MatchMode mode;\n\n        private string type;\n\n        public AddEditClassifier(Window window, bool editMode)\n        {\n            this.window = window;\n            if (window != null)\n            {\n                window.Title = $\"{(editMode ? \"Edit\" : \"Register\")} Classifier\";\n            }\n\n            Accept = new DelegateCommand(AcceptDialog, Validates);\n            Reject = new DelegateCommand(RejectDialog);\n        }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Accept { get; private set; }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                field = value;\n                OnPropertyChanged(nameof(Field));\n            }\n        }\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                mode = value;\n                OnPropertyChanged(nameof(Mode));\n            }\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (value != pattern)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public string Type\n        {\n            get\n            {\n                return type;\n            }\n\n            set\n            {\n                if (value != type)\n                {\n                    type = value;\n                    OnPropertyChanged(nameof(Type));\n                }\n            }\n        }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Reject { get; private set; }\n\n        private void AcceptDialog(object obj)\n        {\n            window.DialogResult = true;\n            window.Close();\n        }\n\n        private void RejectDialog(object obj)\n        {\n            window.DialogResult = false;\n            window.Close();\n        }\n\n        private bool Validates(object obj)\n        {\n            return Name.Length > 0 && Pattern.Length > 0;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Gui/AddEditClassifierWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Classification.Gui.AddEditClassifierWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:sys=\"clr-namespace:System;assembly=mscorlib\"\n        xmlns:interfaces=\"clr-namespace:Sentinel.Interfaces\"\n        Title=\"Add / Edit Classifier\"\n        ResizeMode=\"NoResize\"\n        SizeToContent=\"WidthAndHeight\"\n        WindowStyle=\"SingleBorderWindow\"\n        Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\"\n        WindowStartupLocation=\"CenterOwner\">\n\n    <Window.Resources>\n\n        <ObjectDataProvider x:Key=\"LogEntryFields\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"interfaces:LogEntryFields\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n\n        <ObjectDataProvider x:Key=\"MatchMode\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"interfaces:MatchMode\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n\n    </Window.Resources>\n\n    <DockPanel Height=\"Auto\"\n               Margin=\"5\">\n        <StackPanel Orientation=\"Horizontal\"\n                    DockPanel.Dock=\"Bottom\"\n                    Height=\"36\"\n                    HorizontalAlignment=\"Right\"\n                    Margin=\"0,10,0,0\">\n            <Button Content=\"_OK\"\n                    Command=\"{Binding Accept}\"\n                    Width=\"80\"\n                    Margin=\"0,5\"\n                    IsDefault=\"True\" />\n            <Button Content=\"_Cancel\"\n                    Command=\"{Binding Reject}\"\n                    Width=\"80\"\n                    Margin=\"5,5,0,5\"\n                    IsCancel=\"True\" />\n        </StackPanel>\n\n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n\n            <Label Content=\"Classifier name : \"\n                   Margin=\"5\" />\n            <TextBox Grid.Row=\"0\"\n                     Grid.Column=\"1\"\n                     Text=\"{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     Margin=\"5\"\n                     VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Type to classify : \"\n                   Margin=\"5\"\n                   Grid.Row=\"1\"\n                   Grid.Column=\"0\" />\n            <TextBox Grid.Column=\"1\"\n                     Grid.Row=\"1\"\n                     Text=\"{Binding Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     Margin=\"5\"\n                     VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Field to match against : \"\n                   Grid.Row=\"2\"\n                   Grid.Column=\"0\"\n                   Margin=\"5\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"2\"\n                      SelectedItem=\"{Binding Field, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource LogEntryFields}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match method : \"\n                   Margin=\"5\"\n                   Grid.Row=\"3\"\n                   Grid.Column=\"0\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"3\"\n                      SelectedItem=\"{Binding Mode, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource MatchMode}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match string :\"\n                   Margin=\"5\"\n                   Grid.Row=\"4\"\n                   Grid.Column=\"0\" />\n            <TextBox x:Name=\"MatchText\"\n                     Margin=\"5\"\n                     MinWidth=\"205\"\n                     Grid.Row=\"4\"\n                     Grid.Column=\"1\"\n                     Text=\"{Binding Pattern, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     VerticalContentAlignment=\"Center\" />\n        </Grid>\n    </DockPanel>\n</Window>"
  },
  {
    "path": "Sentinel/Classification/Gui/AddEditClassifierWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Classification.Gui\n{\n    /// <summary>\n    /// Interaction logic for AddEditClassifierWindow.xaml.\n    /// </summary>\n    public partial class AddEditClassifierWindow\n    {\n        public AddEditClassifierWindow()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Classification/Gui/ClassificationsControl.xaml",
    "content": "﻿<UserControl xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:wpf=\"clr-namespace:Sentinel.Support.Wpf\"\n             x:Class=\"Sentinel.Classification.Gui.ClassificationsControl\"\n             mc:Ignorable=\"d\">\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition />\n            <ColumnDefinition Width=\"80\" />\n        </Grid.ColumnDefinitions>\n        <StackPanel Grid.Row=\"0\"\n                    Grid.Column=\"0\"\n                    Grid.RowSpan=\"4\"\n                    Orientation=\"Vertical\"\n                    VerticalAlignment=\"Center\">\n            <Button Command=\"{Binding Classifier.OrderEarlier}\"\n                    Margin=\"5\"\n                    Content=\"Up\"/>\n            <Button Command=\"{Binding Classifier.OrderLater}\"\n                    Margin=\"5\"\n                    Content=\"Down\"/>\n        </StackPanel>\n        <ListView Grid.Row=\"0\"\n                  SelectedIndex=\"{Binding Classifier.SelectedIndex, Mode=OneWayToSource}\"\n                  Grid.Column=\"1\"\n                  Grid.RowSpan=\"4\"\n                  MinHeight=\"200\"\n                  ItemsSource=\"{Binding Classifier.Classifiers}\"\n                  SelectionMode=\"Single\">\n            <ListView.View>\n                <GridView>\n                    <wpf:FixedWidthColumn FixedWidth=\"28\">\n                        <wpf:FixedWidthColumn.HeaderTemplate>\n                            <DataTemplate>\n                                <CheckBox IsChecked=\"True\"\n                                          IsEnabled=\"False\"\n                                          ToolTipService.ShowOnDisabled=\"True\"\n                                          ToolTip=\"Is the classifier enabled?\" />\n                            </DataTemplate>\n                        </wpf:FixedWidthColumn.HeaderTemplate>\n                        <wpf:FixedWidthColumn.CellTemplate>\n                            <DataTemplate>\n                                <CheckBox IsChecked=\"{Binding Enabled}\"\n                                          Margin=\"2,3,0,3\" />\n                            </DataTemplate>\n                        </wpf:FixedWidthColumn.CellTemplate>\n                    </wpf:FixedWidthColumn>\n                    <GridViewColumn Header=\"Name\"\n                                    DisplayMemberBinding=\"{Binding Name}\" />\n                    <GridViewColumn Header=\"Type\"\n                                    DisplayMemberBinding=\"{Binding Type}\" />\n                    <GridViewColumn Header=\"Field\"\n                                    DisplayMemberBinding=\"{Binding Field}\" />\n                    <GridViewColumn Header=\"Mode\"\n                                    DisplayMemberBinding=\"{Binding Mode}\" />\n                    <GridViewColumn Header=\"Description\"\n                                    DisplayMemberBinding=\"{Binding Description}\" />\n                </GridView>\n            </ListView.View>\n        </ListView>\n        <Button Content=\"_Add\"\n                Command=\"{Binding Classifier.Add}\"\n                Grid.Row=\"0\"\n                Margin=\"5,0,5,5\"\n                Grid.Column=\"2\" />\n        <Button Content=\"_Edit\"\n                Command=\"{Binding Classifier.Edit}\"\n                Grid.Row=\"1\"\n                Margin=\"5,0,5,5\"\n                Grid.Column=\"2\" />\n        <Button Content=\"_Remove\"\n                Command=\"{Binding Classifier.Remove}\"\n                Grid.Row=\"2\"\n                Margin=\"5,0,5,5\"\n                Grid.Column=\"2\" />\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/Classification/Gui/ClassificationsControl.xaml.cs",
    "content": "﻿namespace Sentinel.Classification.Gui\n{\n    using System.Windows.Controls;\n\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for ClassificationsControl.xaml.\n    /// </summary>\n    public partial class ClassificationsControl : UserControl\n    {\n        public ClassificationsControl()\n        {\n            InitializeComponent();\n            Classifier = ServiceLocator.Instance.Get<IClassifyingService<IClassifier>>();\n            DataContext = this;\n        }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        // ReSharper disable once UnusedAutoPropertyAccessor.Global\n        public IClassifyingService<IClassifier> Classifier { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Gui/EditClassifier.cs",
    "content": "﻿namespace Sentinel.Classification.Gui\n{\n    using System.Diagnostics;\n    using System.Windows;\n\n    using Sentinel.Classification.Interfaces;\n\n    public class EditClassifier : IEditClassifyingService\n    {\n        public void Edit(IClassifier classifier)\n        {\n            Debug.Assert(classifier != null, \"Extractor must be supplied to allow editing.\");\n\n            var window = new AddEditClassifierWindow();\n            var data = new AddEditClassifier(window, true);\n            window.DataContext = data;\n            window.Owner = Application.Current.MainWindow;\n\n            data.Name = classifier.Name;\n            data.Field = classifier.Field;\n            data.Pattern = classifier.Pattern;\n            data.Mode = classifier.Mode;\n            data.Type = classifier.Type;\n\n            var dialogResult = window.ShowDialog();\n\n            if (dialogResult != null && (bool)dialogResult)\n            {\n                classifier.Name = data.Name;\n                classifier.Pattern = data.Pattern;\n                classifier.Mode = data.Mode;\n                classifier.Field = data.Field;\n                classifier.Type = data.Type;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Classification/Gui/RemoveClassifier.cs",
    "content": "﻿namespace Sentinel.Classification.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Services;\n\n    public class RemoveClassifier : IRemoveClassifyingService\n    {\n        public void Remove(IClassifier classifier)\n        {\n            var service = ServiceLocator.Instance.Get<IClassifyingService<IClassifier>>();\n\n            if (service != null)\n            {\n                var prompt =\n                    \"Are you sure you want to remove the selected classifier?\\r\\n\\r\\n\" +\n                    $\"Classifier Name = \\\"{classifier.Name}\\\"\";\n\n                var result = MessageBox.Show(\n                    prompt,\n                    \"Remove Extractor\",\n                    MessageBoxButton.YesNo,\n                    MessageBoxImage.Question,\n                    MessageBoxResult.No);\n\n                if (result == MessageBoxResult.Yes)\n                {\n                    service.Classifiers.Remove(classifier);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Interfaces/IAddClassifyingService.cs",
    "content": "namespace Sentinel.Classification.Interfaces\n{\n    public interface IAddClassifyingService\n    {\n        void Add();\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Interfaces/IClassifier.cs",
    "content": "namespace Sentinel.Classification.Interfaces\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces;\n\n    public interface IClassifier\n    {\n        [DataMember]\n        string Name { get; set; }\n\n        [DataMember]\n        string Type { get; set; }\n\n        [DataMember]\n        bool Enabled { get; set; }\n\n        [DataMember]\n        string Pattern { get; set; }\n\n        [DataMember]\n        string Description { get; }\n\n        [DataMember]\n        LogEntryFields Field { get; set; }\n\n        [DataMember]\n        MatchMode Mode { get; set; }\n\n        bool IsMatch(ILogEntry entry);\n\n        ILogEntry Classify(ILogEntry entry);\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Interfaces/IClassifyingService.cs",
    "content": "namespace Sentinel.Classification.Interfaces\n{\n    using System.Collections.ObjectModel;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n\n    using Sentinel.Interfaces;\n\n    public interface IClassifyingService<T>\n    {\n        ICommand Add { get; }\n\n        ICommand Edit { get; }\n\n        [DataMember]\n        ObservableCollection<T> Classifiers { get; }\n\n        ICommand OrderEarlier { get; }\n\n        ICommand OrderLater { get; }\n\n        ICommand Remove { get; }\n\n        int SelectedIndex { get; set; }\n\n        ILogEntry Classify(ILogEntry entry);\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Interfaces/IEditClassifyingService.cs",
    "content": "namespace Sentinel.Classification.Interfaces\n{\n    public interface IEditClassifyingService\n    {\n        void Edit(IClassifier classifier);\n    }\n}"
  },
  {
    "path": "Sentinel/Classification/Interfaces/IRemoveClassifyingService.cs",
    "content": "namespace Sentinel.Classification.Interfaces\n{\n    public interface IRemoveClassifyingService\n    {\n        void Remove(IClassifier classifier);\n    }\n}"
  },
  {
    "path": "Sentinel/Controls/AboutWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Controls.AboutWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        Title=\"About\"\n        Height=\"222\"\n        Width=\"450\"\n        SizeToContent=\"Height\"\n        ResizeMode=\"NoResize\"\n        WindowStartupLocation=\"CenterOwner\" >\n\n    <Grid x:Name=\"LayoutRoot\" Margin=\"0,0,0,4\">\n        <DockPanel Margin=\"5\">\n            <StackPanel DockPanel.Dock=\"Top\">\n                <TextBlock Margin=\"5,5,0,0\" FontSize=\"18\" FontWeight=\"Bold\">\n                    <Run Name=\"AssemblyNameLabel\" Text=\"Assembly Name\"/>\n                </TextBlock>\n                <TextBlock Margin=\"5,0,0,10\" FontSize=\"14\" TextWrapping=\"WrapWithOverflow\">                    \n                    <Run Name=\"DescriptionLabel\" Text=\"Description\" />\n                </TextBlock>\n                <TextBlock Margin=\"5\" FontSize=\"14\" TextWrapping=\"WrapWithOverflow\">\n                    <Run Text=\"Version - \" />\n                    <Run Name=\"VersionNumberLabel\" Text=\"\" />\n                </TextBlock>\n                <TextBlock Margin=\"5\"\n                           FontSize=\"14\"\n                           TextWrapping=\"WrapWithOverflow\">\n                    <Run Text=\"Created By - \" />\n                    <Run Name=\"CreatorInfoLabel\"\n                         Text=\"Ray Hayes\" />\n                </TextBlock>\n                <TextBlock Margin=\"5\" FontSize=\"14\" TextWrapping=\"WrapWithOverflow\">\n                    <Run Text=\"Contributors - \" />\n                    <Run Name=\"DeveloperInfoLabel\" Text=\"Ishmal Lewis\" />\n                </TextBlock>\n                <TextBlock Margin=\"5\" FontSize=\"14\" TextWrapping=\"WrapWithOverflow\">\n                    <Run Name=\"CopyrightInfoLabel\" Text=\"Copyright info\" />\n                </TextBlock>\n            </StackPanel>\n        </DockPanel>\n    </Grid>\n</Window>\n"
  },
  {
    "path": "Sentinel/Controls/AboutWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    using System.Diagnostics;\n    using System.IO;\n    using System.Reflection;\n    using System.Windows;\n\n    /// <summary>\n    /// Interaction logic for AboutWindow.xaml.\n    /// </summary>\n    public partial class AboutWindow\n    {\n        public AboutWindow(Window parent)\n        {\n            InitializeComponent();\n\n            Owner = parent;\n\n            try\n            {\n                var assembly = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);\n                AssemblyNameLabel.Text = assembly.ProductName;\n                VersionNumberLabel.Text = assembly.ProductVersion;\n                DescriptionLabel.Text = assembly.Comments;\n                DeveloperInfoLabel.Text = assembly.CompanyName;\n                CopyrightInfoLabel.Text = assembly.LegalCopyright;\n            }\n            catch (FileNotFoundException)\n            {\n                // Can be thrown by the GetVersionInfo call\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Controls/ImageTypesControl.xaml",
    "content": "﻿<UserControl xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n\t\t\t xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n\t\t\t xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n\t\t\t xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n\t\t\t mc:Ignorable=\"d\"\n\t\t\t x:Class=\"Sentinel.Controls.ImageTypesControl\"\n\t\t\t x:Name=\"UserControl\"\n\t\t\t d:DesignWidth=\"445.9\"\n\t\t\t d:DesignHeight=\"80.88\">\n\n\t<UserControl.Resources>\n\t\t<Style TargetType=\"{x:Type ListView}\"\n\t\t\t   BasedOn=\"{StaticResource {x:Type ListBox}}\">\n\t\t\t<Setter Property=\"BorderBrush\"\n\t\t\t\t\tValue=\"Black\" />\n\t\t\t<Setter Property=\"BorderThickness\"\n\t\t\t\t\tValue=\"0.5\" />\n\t\t\t<Setter Property=\"Template\">\n\t\t\t\t<Setter.Value>\n\t\t\t\t\t<ControlTemplate>\n\t\t\t\t\t\t<Border Name=\"bd\"\n\t\t\t\t\t\t\t\tBorderBrush=\"{TemplateBinding BorderBrush}\"\n\t\t\t\t\t\t\t\tBorderThickness=\"{TemplateBinding BorderThickness}\"\n\t\t\t\t\t\t\t\tBackground=\"{TemplateBinding Background}\"\n\t\t\t\t\t\t\t\tMargin=\"{TemplateBinding Margin}\">\n\t\t\t\t\t\t\t<ScrollViewer Margin=\"{TemplateBinding Padding}\">\n\t\t\t\t\t\t\t\t<WrapPanel ItemWidth=\"100\"\n\t\t\t\t\t\t\t\t\t\t   IsItemsHost=\"True\"\n\t\t\t\t\t\t\t\t\t\t   MinWidth=\"100\"\n\t\t\t\t\t\t\t\t\t\t   Width=\"{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}\">\n\t\t\t\t\t\t\t\t</WrapPanel>\n\t\t\t\t\t\t\t</ScrollViewer>\n\t\t\t\t\t\t</Border>\n\t\t\t\t\t</ControlTemplate>\n\t\t\t\t</Setter.Value>\n\t\t\t</Setter>\n\t\t</Style>\n\n\t\t<Style TargetType='{x:Type ListViewItem}'\n\t\t\t   BasedOn='{StaticResource {x:Type ListBoxItem}}'>\n\t\t\t<Setter Property='Padding'\n\t\t\t\t\tValue='3' />\n\t\t\t<Setter Property='Margin'\n\t\t\t\t\tValue='5' />\n\t\t\t<Setter Property='HorizontalContentAlignment'\n\t\t\t\t\tValue='Center' />\n\t\t\t<Setter Property=\"ContentTemplate\">\n\t\t\t\t<Setter.Value>\n\t\t\t\t\t<DataTemplate>\n\t\t\t\t\t\t<StackPanel Orientation=\"Vertical\">\n\t\t\t\t\t\t\t<Image Margin=\"3\"\n\t\t\t\t\t\t\t\t   Width=\"16\"\n\t\t\t\t\t\t\t\t   Height=\"16\"\n\t\t\t\t\t\t\t\t   VerticalAlignment=\"Center\"\n\t\t\t\t\t\t\t\t   HorizontalAlignment=\"Center\"\n\t\t\t\t\t\t\t\t   Source=\"{Binding Image}\" />\n\t\t\t\t\t\t\t<Label Content=\"{Binding Name}\"\n\t\t\t\t\t\t\t\t   VerticalAlignment=\"Center\"\n\t\t\t\t\t\t\t\t   HorizontalAlignment=\"Center\" />\n\t\t\t\t\t\t\t<Label Content=\"{Binding Quality}\"\n\t\t\t\t\t\t\t\t   FontSize=\"10\"\n\t\t\t\t\t\t\t\t   VerticalAlignment=\"Center\"\n\t\t\t\t\t\t\t\t   HorizontalAlignment=\"Center\" />\n\t\t\t\t\t\t</StackPanel>\n\t\t\t\t\t</DataTemplate>\n\t\t\t\t</Setter.Value>\n\t\t\t</Setter>\n\t\t</Style>\n\t</UserControl.Resources>\n\n\t<Grid x:Name=\"LayoutRoot\">\n\t\t<Grid d:LayoutOverrides=\"Height\">\n\t\t\t<Grid.ColumnDefinitions>\n\t\t\t\t<ColumnDefinition />\n\t\t\t\t<ColumnDefinition Width=\"80\" />\n\t\t\t</Grid.ColumnDefinitions>\n\t\t\t<Grid.RowDefinitions>\n\t\t\t\t<RowDefinition Height=\"Auto\" />\n\t\t\t\t<RowDefinition Height=\"Auto\" />\n\t\t\t\t<RowDefinition Height=\"Auto\" />\n\t\t\t\t<RowDefinition Height=\"*\" />\n\t\t\t</Grid.RowDefinitions>\n\n\t\t\t<ListView Grid.RowSpan=\"4\"\n\t\t\t\t\t  ItemsSource=\"{Binding Images.ImageMappings}\"\n\t\t\t\t\t  SelectedIndex=\"{Binding Images.SelectedIndex}\"\t\t\t\t\t  \n\t\t\t\t\t  SelectionMode=\"Single\">\n\t\t\t</ListView>\n\n\t\t\t<Button Grid.Column=\"1\"\n\t\t\t\t\tContent=\"_Add\"\n\t\t\t\t\tCommand=\"{Binding Images.Add}\"\n\t\t\t\t\tMargin=\"5,0,5,5\" />\n\t\t\t<Button Grid.Column=\"1\"\n\t\t\t\t\tCommand=\"{Binding Images.Edit}\"\n\t\t\t\t\tGrid.Row=\"1\"\n\t\t\t\t\tContent=\"_Edit\"\n\t\t\t\t\tMargin=\"5,0,5,5\" />\n\t\t\t<Button Grid.Column=\"1\"\n\t\t\t\t\tGrid.Row=\"2\"\n\t\t\t\t\tCommand=\"{Binding Images.Remove}\"\n\t\t\t\t\tContent=\"_Remove\"\n\t\t\t\t\tMargin=\"5,0,5,5\" />\n\t\t</Grid>\n\t</Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/Controls/ImageTypesControl.xaml.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    using System.Windows.Controls;\n\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for ImageTypesControl.xaml.\n    /// </summary>\n    public partial class ImageTypesControl : UserControl\n    {\n        public ImageTypesControl()\n        {\n            InitializeComponent();\n            Images = ServiceLocator.Instance.Get<ITypeImageService>();\n            DataContext = this;\n        }\n\n        public ITypeImageService Images { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Controls/IntegerTextBox.cs",
    "content": "﻿namespace Sentinel.Controls\r\n{\r\n    using System.Linq;\r\n    using System.Windows.Controls;\r\n    using System.Windows.Input;\r\n\r\n    public class IntegerTextBox : TextBox\r\n    {\r\n        protected override void OnPreviewTextInput(TextCompositionEventArgs e)\r\n        {\r\n            e.Handled = !e.Text.All(char.IsDigit);\r\n\r\n            base.OnPreviewTextInput(e);\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Controls/LogActivityControl.xaml",
    "content": "﻿<UserControl\r\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\r\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\r\n    xmlns:converters=\"clr-namespace:Sentinel.Support.Converters\"\r\n    xmlns:support=\"clr-namespace:Sentinel.Support\"\r\n    mc:Ignorable=\"d\"\r\n    x:Class=\"Sentinel.Controls.LogActivityControl\"\r\n    x:Name=\"UserControl\"\r\n    d:DesignWidth=\"376.654\"\r\n    d:DesignHeight=\"50\">\r\n\r\n    <UserControl.Resources>\r\n        <Style\r\n            x:Key=\"ActivityRowStyle\"\r\n            TargetType=\"{x:Type ListViewItem}\">\r\n            <Style.Triggers>\r\n                <DataTrigger\r\n                    Binding=\"{Binding Preferences.CurrentThemeName, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}\"\r\n                    Value=\"Aero\">\r\n                    <!-- Get rid of the space between rows -->\r\n                    <Setter\r\n                        Property=\"Margin\"\r\n                        Value=\"0,-1,0,-1\" />\r\n                </DataTrigger>\r\n\r\n                <!--<DataTrigger Binding=\"{Binding Highlight.Active, RelativeSource={RelativeSource AncestorType={x:Type Controls:LogActivityControl}, Mode=FindAncestor}}\"\r\n                             Value=\"true\">\r\n                    <Setter Property=\"Background\"\r\n                            Value=\"{Binding LastActivityType, Converter={StaticResource typeToBackgroundConverter}}\" />\r\n                    <Setter Property=\"Foreground\"\r\n                            Value=\"{Binding LastActivityType, Converter={StaticResource typeToForegroundConverter}}\" />\r\n                </DataTrigger>-->\r\n\r\n            </Style.Triggers>\r\n        </Style>\r\n\r\n        <converters:TimeSinceDateTimeConverter\r\n            x:Key=\"TimeSinceDateTimeConverter\" />\r\n\r\n    </UserControl.Resources>\r\n\r\n    <Grid\r\n        x:Name=\"LayoutRoot\">\r\n\r\n        <ListView\r\n            x:Name=\"Updates\"\r\n            support:GridViewSort.AutoSort=\"True\"\r\n            support:GridViewSort.SortGlyphAscending=\"/Resources/Glyphs/SortUpArrow.png\"\r\n            support:GridViewSort.SortGlyphDescending=\"/Resources/Glyphs/SortDownArrow.png\"\r\n            ItemContainerStyle=\"{StaticResource ActivityRowStyle}\"\r\n            ItemsSource=\"{Binding Activity.Details}\">\r\n            <ListView.ContextMenu>\r\n                <ContextMenu\r\n                    x:Name=\"ActivityLbContextMenu\">\r\n                    <MenuItem\r\n                        Header=\"Clear\"\r\n                        Command=\"{Binding ClearActivity}\" />\r\n                </ContextMenu>\r\n            </ListView.ContextMenu>\r\n            <ListView.View>\r\n                <GridView\r\n                    AllowsColumnReorder=\"False\">\r\n                    <GridViewColumn\r\n                        Header=\"System\"\r\n                        DisplayMemberBinding=\"{Binding System}\" />\r\n                    <GridViewColumn\r\n                        Header=\"Classification\"\r\n                        DisplayMemberBinding=\"{Binding Classification}\" />\r\n                    <GridViewColumn\r\n                        Header=\"Since last seen\"\r\n                        support:GridViewSort.PropertyName=\"LastActivity\"\r\n                        DisplayMemberBinding=\"{Binding LastActivity, Converter={StaticResource TimeSinceDateTimeConverter}}\" />\r\n                    <GridViewColumn\r\n                        Header=\"Since Error\"\r\n                        support:GridViewSort.PropertyName=\"LastError\"\r\n                        DisplayMemberBinding=\"{Binding LastError, Converter={StaticResource TimeSinceDateTimeConverter}}\" />\r\n                    <GridViewColumn\r\n                        Header=\"Since Warning\"\r\n                        support:GridViewSort.PropertyName=\"LastWarning\"\r\n                        DisplayMemberBinding=\"{Binding LastWarning, Converter={StaticResource TimeSinceDateTimeConverter}}\" />\r\n                </GridView>\r\n            </ListView.View>\r\n        </ListView>\r\n    </Grid>\r\n</UserControl>"
  },
  {
    "path": "Sentinel/Controls/LogActivityControl.xaml.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for LogActivityControl.xaml.\n    /// </summary>\n    public partial class LogActivityControl\n    {\n        public LogActivityControl()\n        {\n            InitializeComponent();\n            Highlight = ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n        }\n\n        public IHighlightingService<IHighlighter> Highlight { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Controls/MainWindow.xaml",
    "content": "﻿<Window\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:sys=\"clr-namespace:System;assembly=mscorlib\"\n    xmlns:sentinelInterfaces=\"clr-namespace:Sentinel.Interfaces\"\n    xmlns:converters=\"clr-namespace:Sentinel.Support.Converters\"\n    x:Class=\"Sentinel.Controls.MainWindow\"\n    Title=\"Sentinel\"\n    Height=\"600\"\n    Width=\"1024\"\n    mc:Ignorable=\"d\"\n    OverridesDefaultStyle=\"False\"\n    Loaded=\"OnLoaded\"\n    Closing=\"OnClosed\"\n    ResizeMode=\"CanResizeWithGrip\">\n\n    <Window.Resources>\n        <Style\n            TargetType=\"{x:Type Image}\">\n            <Setter\n                Property=\"Width\"\n                Value=\"16\" />\n            <Setter\n                Property=\"Height\"\n                Value=\"16\" />\n            <Setter\n                Property=\"RenderOptions.BitmapScalingMode\"\n                Value=\"NearestNeighbor\" />\n\n            <Style.Triggers>\n                <!-- If the parent is a button, then disable image (visually) when parent is not enabled -->\n                <DataTrigger\n                    Binding=\"{Binding IsEnabled, RelativeSource={RelativeSource AncestorLevel=1, AncestorType={x:Type Button}}}\"\n                    Value=\"False\">\n                    <Setter\n                        Property=\"Opacity\"\n                        Value=\"0.50\" />\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        \n        <ObjectDataProvider x:Key=\"LogEntryField\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"sentinelInterfaces:LogEntryFields\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n        <ObjectDataProvider x:Key=\"MatchMode\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"sentinelInterfaces:MatchMode\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n\n        <converters:TypeToLargeImageConverter\n            x:Key=\"TypeToLargeImage\" />\n        <converters:TypeToMediumImageConverter\n            x:Key=\"TypeToMediumImage\" />\n        <converters:TypeToSmallImageConverter\n            x:Key=\"TypeToSmallImage\" />\n        <converters:LongPathToShortPathConverter\n            x:Key=\"LongPathToShortPath\" />\n    </Window.Resources>\n\n    <DockPanel>\n\n        <Ribbon\n            SelectedIndex=\"0\"\n            DockPanel.Dock=\"Top\"\n            ShowQuickAccessToolBarOnTop=\"False\" Margin=\"0,-22,0,0\">\n\n            <Ribbon.ApplicationMenu>\n                <RibbonApplicationMenu>\n\n                    <RibbonApplicationMenu.SmallImageSource>\n                        <DrawingImage>\n                            <DrawingImage.Drawing>\n                                <GeometryDrawing>\n                                    <GeometryDrawing.Geometry>\n                                        <RectangleGeometry Rect=\"0,0,20,20\"></RectangleGeometry>\n                                    </GeometryDrawing.Geometry>\n                                    <GeometryDrawing.Brush>\n                                        <VisualBrush Stretch=\"Uniform\">\n                                            <VisualBrush.Visual>\n                                                <TextBlock Text=\"File\" FontSize=\"16\" Foreground=\"White\" />\n                                            </VisualBrush.Visual>\n                                        </VisualBrush>\n                                    </GeometryDrawing.Brush>\n                                </GeometryDrawing>\n                            </DrawingImage.Drawing>\n                        </DrawingImage>\n                    </RibbonApplicationMenu.SmallImageSource>\n\n                    <RibbonApplicationMenuItem\n                        Header=\"New Session\"\n                        ImageSource=\"/Resources/Large/Add.PNG\"\n                        KeyTip=\"N\"\n                        Command=\"{Binding NewSession}\"/>\n\n                    <RibbonApplicationMenuItem\n                        Header=\"Load Session\"\n                        ImageSource=\"/Resources/Large/Open.PNG\"\n                        KeyTip=\"L\"\n                        Command=\"{Binding LoadSession}\"/>\n\n                    <RibbonApplicationMenuItem\n                        Header=\"Save Session\"\n                        ImageSource=\"/Resources/Large/Save.PNG\"\n                        KeyTip=\"S\"\n                        Command=\"{Binding SaveSession}\"/>                    \n                    \n                    <RibbonApplicationMenuItem\n                        Header=\"Export Logs\"\n                        ImageSource=\"/Resources/Large/Export.PNG\"\n                        Command=\"{Binding ExportLogs}\"\n                        KeyTip=\"E\" />\n                    \n                    <RibbonApplicationMenuItem\n                        Header=\"Preferences\"\n                        Command=\"{Binding ShowPreferences}\"\n                        CommandParameter=\"0\"\n                        ImageSource=\"/Resources/Large/Settings.PNG\"\n                        ToolTip=\"Show Preferences Dialog\" />\n\n                    <RibbonApplicationMenuItem\n                        Header=\"About\"\n                        ImageSource=\"/Resources/Large/Info.PNG\"\n                        KeyTip=\"A\"\n                        Command=\"{Binding About}\"/>\n\n                    <RibbonApplicationMenu.FooterPaneContent>\n                        <Grid>\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition\n                                    Width=\"Auto\" />\n                                <ColumnDefinition\n                                    Width=\"*\" />\n                                <ColumnDefinition\n                                    Width=\"Auto\" />\n                            </Grid.ColumnDefinitions>\n                            <RibbonButton\n                                SmallImageSource=\"/Resources/Small/Exit.PNG\"\n                                Label=\"Exit\"\n                                HorizontalAlignment=\"Right\"\n                                Command=\"{Binding Exit}\"\n                                Grid.Column=\"2\" />\n                        </Grid>\n                    </RibbonApplicationMenu.FooterPaneContent>\n\n                    <RibbonApplicationMenu.AuxiliaryPaneContent>\n                        <StackPanel>\n                            <TextBlock HorizontalAlignment=\"Center\" Text=\"Recently Opened Sessions\" FontSize=\"16\" Opacity=\"0.40\" />\n                            <RibbonGroup ItemsSource=\"{Binding RecentFiles}\">\n                                <RibbonGroup.ItemTemplate>\n                                    <DataTemplate>\n                                        <RibbonButton Label=\"{Binding ., Converter={StaticResource LongPathToShortPath}}\"\n                                                      Command=\"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.LoadSession}\"\n                                                      CommandParameter=\"{Binding .}\"\n                                                      HorizontalContentAlignment=\"Left\"\n                                                      VerticalContentAlignment=\"Bottom\"\n                                                  ToolTipTitle=\"{Binding .}\"\n                                                  ToolTipDescription=\"Click to open this session.\" />                                        \n                                    </DataTemplate>\n                                </RibbonGroup.ItemTemplate>                                \n                            </RibbonGroup>                            \n                        </StackPanel>\n                    </RibbonApplicationMenu.AuxiliaryPaneContent>\n                    \n                </RibbonApplicationMenu>\n            </Ribbon.ApplicationMenu>\n\n            <RibbonTab Header=\"Home\">\n                <RibbonGroup Header=\"General\">\n                    <RibbonButton Label=\"Add Provider\"\n                                    ToolTip=\"Add a new log viewer\"\n                                    SmallImageSource=\"/Resources/Small/Add.png\"\n                                    Command=\"{Binding Add}\" />\n                    \n                    <RibbonButton ToolTip=\"Export the logs currently in the viewer to a file\"\n                                          Command=\"{Binding ExportLogs}\"\n                                          SmallImageSource=\"/Resources/Small/Export.png\"\n                                          Label=\"Export Logs\" />\n                    \n                    <RibbonButton ToolTip=\"Show the Preferences dialog\"\n                                          Command=\"{Binding ShowPreferences}\"\n                                          CommandParameter=\"0\"\n                                          SmallImageSource=\"/Resources/Small/Settings.png\"\n                                          Label=\"Preferences\" />\n                </RibbonGroup>\n\n                <!-- Standard highlighters -->\n                <RibbonGroup Name=\"StandardHighlightersRibbonGroup\" Header=\"Highlighters\"> <!--ItemsSource=\"{Binding Source={StaticResource standardHighlighters}}\"-->                               \n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton Label=\"{Binding Name}\"\n                                                  IsChecked=\"{Binding Enabled}\"\n                                                  HorizontalContentAlignment=\"Center\"\n                                                  VerticalContentAlignment=\"Bottom\"\n                                                  SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                                  LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                                  ToolTipTitle=\"{Binding Name, StringFormat=Highlighter for {0}}\"\n                                                  ToolTipDescription=\"{Binding Description}\"\n                                                  ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                                  ToolTipFooterTitle=\"Using Highlighters\"\n                                                  ToolTipFooterDescription=\"Note, when a highlighter is enabled, matching messages are highlighted.\"\n                                                  ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n                <RibbonGroup Name=\"StandardFiltersRibbonGroup\" Header=\"Filters\"><!--ItemsSource=\"{Binding Source={StaticResource standardFilters}}\"-->\n                               \n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton Label=\"{Binding Name}\"\n                                                  IsChecked=\"{Binding Enabled}\"\n                                                  HorizontalContentAlignment=\"Center\"\n                                                  VerticalContentAlignment=\"Bottom\"\n                                                  SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                                  LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                                  ToolTipTitle=\"{Binding Name, StringFormat=Filter for {0}}\"\n                                                  ToolTipDescription=\"{Binding Description}\"\n                                                  ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                                  ToolTipFooterTitle=\"Using Filters\"\n                                                  ToolTipFooterDescription=\"Note, when a filter is enabled, matching messages are omitted.\"\n                                                  ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n                <RibbonGroup Header=\"Search\" IsEnabled=\"True\">\n                    <StackPanel>\n                        <RibbonTextBox Name=\"SearchRibbonTextBox\" TextBoxWidth=\"200\" />\n                        \n                        <ComboBox Name=\"SearchTargetComboBox\" HorizontalAlignment=\"Left\" Margin=\"5,0,0,0\" MinWidth=\"100\" IsEditable=\"True\" IsReadOnly=\"True\" ItemsSource=\"{Binding Source={StaticResource LogEntryField}}\" />\n\n                        <StackPanel Orientation=\"Horizontal\">\n\n                            <RibbonToggleButton x:Name=\"HighlightToggleButton\" Margin=\"5,0,0,0\" Label=\"Highlight\"\n                                              HorizontalAlignment=\"Left\"                                               \n                                              VerticalContentAlignment=\"Center\" Checked=\"SearchToggleButtonChecked\"/>\n                            <RibbonToggleButton x:Name=\"FilterToggleButton\" Margin=\"20,0,0,0\" Label=\"Filter\"\n                                              HorizontalAlignment=\"Left\"                                              \n                                              VerticalContentAlignment=\"Center\" Checked=\"SearchToggleButtonChecked\" />\n                            <RibbonToggleButton x:Name=\"ExtractToggleButton\" Margin=\"20,0,0,0\" Label=\"Extract\"\n                                              HorizontalAlignment=\"Left\"                                              \n                                              VerticalContentAlignment=\"Center\" Checked=\"SearchToggleButtonChecked\" />                            \n                        </StackPanel>\n                    </StackPanel>\n                    \n                    <ListBox Name=\"SearchModeListBox\" Margin=\"2,2,2,0\" ItemsSource=\"{Binding Source={StaticResource MatchMode}}\" />\n                </RibbonGroup>\n                <RibbonGroup Header=\"Show Columns\">\n                    <RibbonToggleButton Name=\"ExceptionRibbonToggleButton\" Label=\"Exception\"                                          \n                                          HorizontalContentAlignment=\"Center\"\n                                          VerticalContentAlignment=\"Bottom\"\n                                          SmallImageSource=\"../Resources/Small/Exception.png\"\n                                          LargeImageSource=\"../Resources/Large/Exception.png\"\n                                          ToolTipTitle=\"Show Exceptions\"\n                                          ToolTipDescription=\"Shows the Exception column when enabled.\"\n                                          ToolTipImageSource=\"../Resources/Small/Exception.png\" />\n                    <RibbonToggleButton Name=\"ThreadRibbonToggleButton\" Label=\"Thread\"\n                                          HorizontalContentAlignment=\"Center\"\n                                          VerticalContentAlignment=\"Bottom\"\n                                          SmallImageSource=\"../Resources/Small/Thread.png\"\n                                          LargeImageSource=\"../Resources/Large/Thread.png\"\n                                          ToolTipTitle=\"Show Threads\"\n                                          ToolTipDescription=\"Shows the Thread column when enabled.\"\n                                          ToolTipImageSource=\"../Resources/Small/Thread.png\" />\n                    <RibbonToggleButton Name=\"SourceHostRibbonToggleButton\"\n                                        Label=\"Source Host\"\n                                        HorizontalContentAlignment=\"Center\"\n                                        VerticalContentAlignment=\"Bottom\"\n                                        SmallImageSource=\"../Resources/Small/Network.png\"\n                                        LargeImageSource=\"../Resources/Large/Network.png\"\n                                        ToolTipTitle=\"Show source machine name\"\n                                        ToolTipDescription=\"Shows the host name of the machine originating the message.\"\n                                        ToolTipImageSource=\"../Resources/Medium/Network.png\" />\n                    <RibbonToggleButton Name=\"DebugSourceRibbonToggleButton\"\n                                        Label=\"Debug Sources\"\n                                        HorizontalContentAlignment=\"Center\"\n                                        VerticalContentAlignment=\"Bottom\"\n                                        SmallImageSource=\"../Resources/Small/DebugSource.png\"\n                                        LargeImageSource=\"../Resources/Large/DebugSource.png\"\n                                        ToolTipTitle=\"Show source code debug information\"\n                                        ToolTipDescription=\"Shows the debugging columns when enabled.\"\n                                        ToolTipImageSource=\"../Resources/Small/DebugSource.png\" />\n                    <RibbonToggleButton Name=\"ContextRibbonToggleButton\"\n                                        Label=\"Context\"\n                                        HorizontalContentAlignment=\"Center\"\n                                        VerticalContentAlignment=\"Bottom\"\n                                        SmallImageSource=\"../Resources/Small/Context.png\"\n                                        LargeImageSource=\"../Resources/Large/Context.png\"\n                                        ToolTipTitle=\"Show context (hardcoded to property UnitTest)\"\n                                        ToolTipDescription=\"Shows the debugging columns when enabled.\"\n                                        ToolTipImageSource=\"../Resources/Small/Context.png\" />\n                </RibbonGroup>\n            </RibbonTab>\n            <RibbonTab Header=\"Classifiers\">\n                <RibbonGroup Header=\"Options\">\n                    <RibbonButton Label=\"Add New Classifier\"\n                                    LargeImageSource=\"/Resources/Large/Add.png\"\n                                    Command=\"{Binding ShowPreferences}\"\n                                    CommandParameter=\"1\" />\n                </RibbonGroup>\n                <RibbonGroup Name=\"CustomClassifiersRibbonGroupOnTab\" Header=\"Custom Classifiers\">                    \n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton Label=\"{Binding Name}\"\n                                                  IsChecked=\"{Binding Enabled}\"\n                                                  HorizontalContentAlignment=\"Center\"\n                                                  VerticalContentAlignment=\"Bottom\"\n                                                  SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                                  LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                                  ToolTipTitle=\"{Binding Name, StringFormat=Classifier for {0}}\"\n                                                  ToolTipDescription=\"{Binding Description}\"\n                                                  ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                                  ToolTipFooterTitle=\"Using Classifiers\"\n                                                  ToolTipFooterDescription=\"Note, when a classifier is enabled, incoming messages will have their type modified to the type of the last applicable classifier.  This is a custom classifier.\"\n                                                  ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n            </RibbonTab>\n\n            <RibbonTab\n                Header=\"Highlighters\">\n                <RibbonGroup\n                    Header=\"Options\">\n                    <RibbonButton\n                        Label=\"Add New Highlighter\"\n                        Command=\"{Binding ShowPreferences}\"\n                        CommandParameter=\"2\"\n                        LargeImageSource=\"/Resources/Large/Add.png\" />\n                </RibbonGroup>\n                <RibbonGroup\n                    Name=\"StandardHighlighterRibbonGroupOnTab\" Header=\"Standard Highlighters\">\n                    <!--ItemsSource=\"{Binding Source={StaticResource standardHighlighters}}\"\n                    Visibility=\"{Binding Source={StaticResource standardHighlighters}, Path=Count, Converter={StaticResource collapseIfZero}}\">-->                    \n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton\n                                Label=\"{Binding Name}\"\n                                IsChecked=\"{Binding Enabled}\"\n                                HorizontalContentAlignment=\"Center\"\n                                VerticalContentAlignment=\"Bottom\"\n                                SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                ToolTipTitle=\"{Binding Name, StringFormat=Highlighter for {0}}\"\n                                ToolTipDescription=\"{Binding Description}\"\n                                ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                ToolTipFooterTitle=\"Using Highlighters\"\n                                ToolTipFooterDescription=\"Note, when a highlighter is enabled, matching messages are highlighted.  This is a standard highlighter.\"\n                                ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n                <RibbonGroup Name=\"CustomHighlighterRibbonGroupOnTab\" Header=\"Custom Highlighters\">\n                    <!--ItemsSource=\"{Binding Source={StaticResource customHighlighters}}\"\n                    Visibility=\"{Binding Source={StaticResource customHighlighters}, Path=Count, Converter={StaticResource collapseIfZero}}\">-->                    \n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton\n                                Label=\"{Binding Name}\"\n                                IsChecked=\"{Binding Enabled}\"\n                                HorizontalContentAlignment=\"Center\"\n                                VerticalContentAlignment=\"Bottom\"\n                                SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                ToolTipTitle=\"{Binding Name, StringFormat=Highlighter for {0}}\"\n                                ToolTipDescription=\"{Binding Description}\"\n                                ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                ToolTipFooterTitle=\"Using Highlighters\"\n                                ToolTipFooterDescription=\"Note, when a highlighter is enabled, matching messages are highlighted.  This is a custom highlighter.\"\n                                ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n            </RibbonTab>\n            <RibbonTab Header=\"Filters\">\n                <RibbonGroup Header=\"Options\">\n                    <RibbonButton Label=\"Add New Filter\"\n                                  Command=\"{Binding ShowPreferences}\"\n                                    CommandParameter=\"3\"\n                                    LargeImageSource=\"/Resources/Large/Add.png\" />\n                </RibbonGroup>\n                <RibbonGroup Name=\"StandardFiltersRibbonGroupOnTab\" Header=\"Standard Filters\">\n                    <!--ItemsSource=\"{Binding Source={StaticResource standardFilters}}\"\n                    Visibility=\"{Binding Source={StaticResource standardFilters}, Path=Count, Converter={StaticResource collapseIfZero}}\"-->\n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton Label=\"{Binding Name}\"\n                                                  IsChecked=\"{Binding Enabled}\"\n                                                  HorizontalContentAlignment=\"Center\"\n                                                  VerticalContentAlignment=\"Bottom\"\n                                                  SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                                  LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                                  ToolTipTitle=\"{Binding Name, StringFormat=Filter for {0}}\"\n                                                  ToolTipDescription=\"{Binding Description}\"\n                                                  ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                                  ToolTipFooterTitle=\"Using Filters\"\n                                                  ToolTipFooterDescription=\"Note, when a filter is enabled, matching messages are omitted.  This is a standard filter.\"\n                                                  ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n                <RibbonGroup\n                    Name=\"CustomFiltersRibbonGroupOnTab\" Header=\"Custom Filters\">\n                    <!--ItemsSource=\"{Binding Source={StaticResource customFilters}}\"\n                        Visibility=\"{Binding Source={StaticResource customFilters}, Path=Count, Converter={StaticResource collapseIfZero}}\"-->                               \n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton Label=\"{Binding Name}\"\n                                                  IsChecked=\"{Binding Enabled}\"\n                                                  HorizontalContentAlignment=\"Center\"\n                                                  VerticalContentAlignment=\"Bottom\"\n                                                  SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                                  LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                                  ToolTipTitle=\"{Binding Name, StringFormat=Filter for {0}}\"\n                                                  ToolTipDescription=\"{Binding Description}\"\n                                                  ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                                  ToolTipFooterTitle=\"Using Filters\"\n                                                  ToolTipFooterDescription=\"Note, when a filter is enabled, matching messages are omitted.  This is a custom filter.\"\n                                                  ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n            </RibbonTab>\n            <RibbonTab Header=\"Extractors\">\n                <RibbonGroup Header=\"Options\">\n                    <RibbonButton Label=\"Add New Extractor\"\n                                    LargeImageSource=\"/Resources/Large/Add.png\"\n                                    Command=\"{Binding ShowPreferences}\"\n                                    CommandParameter=\"4\" />\n                </RibbonGroup>\n                <RibbonGroup Name=\"CustomExtractorsRibbonGroupOnTab\" Header=\"Custom Extractors\">\n                    <!--ItemsSource=\"{Binding Source={StaticResource customExtractors}}\"-->\n                    <RibbonGroup.ItemTemplate>\n                        <DataTemplate>\n                            <RibbonToggleButton Label=\"{Binding Name}\"\n                                                  IsChecked=\"{Binding Enabled}\"\n                                                  HorizontalContentAlignment=\"Center\"\n                                                  VerticalContentAlignment=\"Bottom\"\n                                                  SmallImageSource=\"{Binding Name, Converter={StaticResource TypeToSmallImage}}\"\n                                                  LargeImageSource=\"{Binding Name, Converter={StaticResource TypeToMediumImage}}\"\n                                                  ToolTipTitle=\"{Binding Name, StringFormat=Extractor for {0}}\"\n                                                  ToolTipDescription=\"{Binding Description}\"\n                                                  ToolTipImageSource=\"{Binding Name, Converter={StaticResource TypeToLargeImage}}\"\n                                                  ToolTipFooterTitle=\"Using Extractors\"\n                                                  ToolTipFooterDescription=\"Note, when an extractor is enabled, only matching messages are shown.  This is a custom extractor.\"\n                                                  ToolTipFooterImageSource=\"/Resources/Small/Info.png\">\n                            </RibbonToggleButton>\n                        </DataTemplate>\n                    </RibbonGroup.ItemTemplate>\n                </RibbonGroup>\n            </RibbonTab>            \n        </Ribbon>\n\n        <DockPanel>\n            <TabControl x:Name=\"TabControl\"\n                        Grid.Row=\"1\">\n            </TabControl>\n        </DockPanel>\n    </DockPanel>\n</Window>"
  },
  {
    "path": "Sentinel/Controls/MainWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.Collections.Specialized;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.IO;\n    using System.Linq;\n    using System.Reflection;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Controls.Primitives;\n    using System.Windows.Controls.Ribbon;\n    using System.Windows.Data;\n    using System.Windows.Input;\n    using CommandLine;\n    using log4net;\n    using Microsoft.Win32;\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Log4Net;\n    using Sentinel.Logs.Interfaces;\n    using Sentinel.NLog;\n    using Sentinel.Providers.Interfaces;\n    using Sentinel.Services;\n    using Sentinel.Services.Interfaces;\n    using Sentinel.StartUp;\n    using Sentinel.Support;\n    using Sentinel.Views.Interfaces;\n    using WpfExtras;\n    using WpfExtras.Converters;\n\n    /// <summary>\n    /// Interaction logic for MainWindow.xaml.\n    /// </summary>\n    public partial class MainWindow\n    {\n        private static readonly ILog Log = log4net.LogManager.GetLogger(typeof(MainWindow));\n\n        private readonly string persistingFilename;\n\n        private readonly string persistingRecentFileName;\n\n        private List<string> recentFilePathList;\n\n        private PreferencesWindow preferencesWindow;\n\n        private int preferencesWindowTabSelected;\n\n        public MainWindow()\n        {\n            InitializeComponent();\n            var savingDirectory = ServiceLocator.Instance.SaveLocation;\n            persistingFilename = Path.Combine(savingDirectory, \"MainWindow\");\n            persistingRecentFileName = Path.Combine(savingDirectory, \"RecentFiles\");\n            var fileName = Path.ChangeExtension(persistingFilename, \".json\");\n\n            var settings = PersistingSettings.Load(fileName);\n\n            // Restore persisted window placement if provided\n            if (settings?.WindowPlacementInfo != null)\n            {\n                RestoreWindowPosition(settings.WindowPlacementInfo);\n            }\n\n            if (settings?.UserPreferences != null)\n            {\n                // TODO: is this already set?\n                Preferences = settings.UserPreferences;\n            }\n\n            // Get recently opened files\n            GetRecentlyOpenedFiles();\n        }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Add { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand About { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand ShowPreferences { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand ExportLogs { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Exit { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand NewSession { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand SaveSession { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand LoadSession { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IUserPreferences Preferences { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IViewManager ViewManager { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IFilteringService<IFilter> Filters => ServiceLocator.Instance.Get<IFilteringService<IFilter>>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IHighlightingService<IHighlighter> Highlighters\n            => ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IClassifyingService<IClassifier> ClassifyingService\n            => ServiceLocator.Instance.Get<IClassifyingService<IClassifier>>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IExtractingService<IExtractor> Extractors\n            => ServiceLocator.Instance.Get<IExtractingService<IExtractor>>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ISearchHighlighter Search => ServiceLocator.Instance.Get<ISearchHighlighter>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ISearchFilter SearchFilter => ServiceLocator.Instance.Get<ISearchFilter>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ISearchExtractor SearchExtractor => ServiceLocator.Instance.Get<ISearchExtractor>();\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ObservableCollection<string> RecentFiles { get; private set; }\n\n        private static WindowPlacementInfo ValidateScreenPosition(WindowPlacementInfo wp)\n        {\n            if (wp == null)\n            {\n                return null;\n            }\n\n            try\n            {\n                var virtualScreen = new Rect(\n                    SystemParameters.VirtualScreenLeft,\n                    SystemParameters.VirtualScreenTop,\n                    SystemParameters.VirtualScreenWidth,\n                    SystemParameters.VirtualScreenHeight);\n                var window = new Rect(wp.Left, wp.Top, wp.Width, wp.Height);\n                return virtualScreen.IntersectsWith(window) ? wp : null;\n            }\n            catch (Exception e)\n            {\n                Log.Error(\"Unable to calculate rectangle or perform intersection with window\", e);\n            }\n\n            return null;\n        }\n\n        private void ShowPreferencesAction(object obj)\n        {\n            preferencesWindowTabSelected = Convert.ToInt32(obj);\n            Preferences.Show = true;\n        }\n\n        private void ExportLogsAction(object obj)\n        {\n            // Get Log\n            var tab = (TabItem)TabControl.SelectedItem;\n            var frame = (IWindowFrame)tab.Content;\n            var restartLogging = false;\n\n            // Notify user that log messages will be paused during this operation\n            if (frame.Log.Enabled)\n            {\n                var messageBoxResult =\n                    MessageBox.Show(\n                        \"The log viewer must be paused momentarily for this operation to continue. Is it OK to pause logging?\",\n                        \"Sentinel\",\n                        MessageBoxButton.YesNo,\n                        MessageBoxImage.Warning);\n\n                if (messageBoxResult == MessageBoxResult.Yes)\n                {\n                    frame.Log.Enabled = false;\n                    restartLogging = true;\n                }\n                else\n                {\n                    return;\n                }\n            }\n\n            // Open a save file dialog\n            var savefile = new SaveFileDialog\n            {\n                FileName = frame.Log.Name,\n                DefaultExt = \".log\",\n                Filter = \"Log documents (.log)|*.log|Text documents (.txt)|*.txt\",\n                FilterIndex = 0,\n            };\n\n            if (savefile.ShowDialog(this) == true)\n            {\n                var logFileExporter = ServiceLocator.Instance.Get<ILogFileExporter>();\n                logFileExporter.SaveLogViewerToFile(frame, savefile.FileName);\n            }\n\n            frame.Log.Enabled = restartLogging;\n        }\n\n        private void SaveSessionAction(object obj)\n        {\n            var sessionManager = ServiceLocator.Instance.Get<ISessionManager>();\n\n            // Open a save file dialog\n            var savefile = new SaveFileDialog\n            {\n                FileName = sessionManager.Name,\n                DefaultExt = \".sntl\",\n                Filter = \"Sentinel session (.sntl)|*.sntl\",\n                FilterIndex = 0,\n            };\n\n            if (savefile.ShowDialog(this) == true)\n            {\n                sessionManager.SaveSession(savefile.FileName);\n                AddToRecentFiles(savefile.FileName);\n            }\n        }\n\n        private void AddToRecentFiles(string fileName)\n        {\n            if (RecentFiles.Contains(fileName))\n            {\n                RecentFiles.Move(RecentFiles.IndexOf(fileName), 0);\n            }\n            else\n            {\n                RecentFiles.Insert(0, fileName);\n            }\n\n            // Keep list at no more than 13\n            if (RecentFiles.Count > 13)\n            {\n                RecentFiles.Remove(RecentFiles.LastOrDefault());\n            }\n        }\n\n        private void NewSessionAction(object obj)\n        {\n            var sessionManager = ServiceLocator.Instance.Get<ISessionManager>();\n\n            if (!sessionManager.IsSaved)\n            {\n                var userResult = MessageBox.Show(\n                    \"Do you want to save changes you made to \" + sessionManager.Name + \"?\",\n                    \"Sentinel\",\n                    MessageBoxButton.YesNoCancel,\n                    MessageBoxImage.Warning);\n\n                if (userResult == MessageBoxResult.Cancel)\n                {\n                    return;\n                }\n\n                if (userResult == MessageBoxResult.Yes)\n                {\n                    SaveSession.Execute(null);\n\n                    // if the user clicked \"Cancel\" at the save dialog box\n                    if (!sessionManager.IsSaved)\n                    {\n                        return;\n                    }\n                }\n            }\n\n            // Remove the tab control.\n            if (TabControl.Items.Count > 0)\n            {\n                var tab = TabControl.SelectedItem;\n                TabControl.Items.Remove(tab);\n            }\n\n            Add.Execute(null);\n        }\n\n        private void LoadSessionAction(object obj)\n        {\n            var sessionManager = ServiceLocator.Instance.Get<ISessionManager>();\n            var fileNameToLoad = (string)obj;\n\n            if (!sessionManager.IsSaved)\n            {\n                var userResult = MessageBox.Show(\n                    \"Do you want to save changes you made to \" + sessionManager.Name + \"?\",\n                    \"Sentinel\",\n                    MessageBoxButton.YesNoCancel,\n                    MessageBoxImage.Warning);\n\n                if (userResult == MessageBoxResult.Cancel)\n                {\n                    return;\n                }\n\n                if (userResult == MessageBoxResult.Yes)\n                {\n                    SaveSession.Execute(null);\n\n                    // if the user clicked \"Cancel\" at the save dialog box\n                    if (!sessionManager.IsSaved)\n                    {\n                        return;\n                    }\n                }\n            }\n\n            if (fileNameToLoad == null)\n            {\n                // open a save file dialog\n                var openFile = new OpenFileDialog\n                {\n                    FileName = sessionManager.Name,\n                    DefaultExt = \".sntl\",\n                    Filter = \"Sentinel session (.sntl)|*.sntl\",\n                    FilterIndex = 0,\n                };\n\n                if (openFile.ShowDialog(this) == true)\n                {\n                    fileNameToLoad = openFile.FileName;\n                }\n                else\n                {\n                    return;\n                }\n            }\n\n            // Remove the tab control.\n            if (TabControl.Items.Count > 0)\n            {\n                var tab = TabControl.SelectedItem;\n                TabControl.Items.Remove(tab);\n            }\n\n            RemoveBindingReferences();\n\n            sessionManager.LoadSession(fileNameToLoad);\n            AddToRecentFiles(fileNameToLoad);\n\n            BindViewToViewModel();\n\n            if (!sessionManager.ProviderSettings.Any())\n            {\n                return;\n            }\n\n            var frame = ServiceLocator.Instance.Get<IWindowFrame>();\n\n            // Add to the tab control.\n            var newTab = new TabItem { Header = sessionManager.Name, Content = frame };\n            TabControl.Items.Add(newTab);\n            TabControl.SelectedItem = newTab;\n        }\n\n        /// <summary>\n        /// AddNewListenerAction method provides a mechanism for the user to add additional\n        /// listeners to the log-viewer.\n        /// </summary>\n        /// <param name=\"obj\">Object to add as a new listener.</param>\n        private void AddNewListenerAction(object obj)\n        {\n            // Load a new session\n            var sessionManager = ServiceLocator.Instance.Get<ISessionManager>();\n\n            RemoveBindingReferences();\n\n            sessionManager.LoadNewSession(this);\n\n            BindViewToViewModel();\n\n            if (!sessionManager.ProviderSettings.Any())\n            {\n                return;\n            }\n\n            var frame = ServiceLocator.Instance.Get<IWindowFrame>();\n\n            // Add to the tab control.\n            var tab = new TabItem { Header = sessionManager.Name, Content = frame };\n            TabControl.Items.Add(tab);\n            TabControl.SelectedItem = tab;\n        }\n\n        private void OnLoaded(object sender, RoutedEventArgs e)\n        {\n            Exit = new DelegateCommand(ee => Close());\n            About = new DelegateCommand(\n                ee =>\n                {\n                    var about = new AboutWindow(this);\n                    about.ShowDialog();\n                });\n\n            Add = new DelegateCommand(AddNewListenerAction, b => TabControl.Items.Count < 1);\n            ShowPreferences = new DelegateCommand(ShowPreferencesAction);\n            ExportLogs = new DelegateCommand(ExportLogsAction, b => TabControl.Items.Count > 0);\n            SaveSession = new DelegateCommand(SaveSessionAction);\n            NewSession = new DelegateCommand(NewSessionAction);\n            LoadSession = new DelegateCommand(LoadSessionAction);\n            RecentFiles = new ObservableCollection<string>(recentFilePathList.Take(13));\n\n            BindViewToViewModel();\n\n            var commandLine = Environment.GetCommandLineArgs();\n            if (commandLine.Length == 1)\n            {\n                Add.Execute(null);\n            }\n            else\n            {\n                ProcessCommandLine(commandLine.Skip(1));\n            }\n\n            // Debug the available loggers.\n            var logManager = ServiceLocator.Instance.Get<Logs.Interfaces.ILogManager>();\n            foreach (var logger in logManager)\n            {\n                Log.DebugFormat(\"Log: {0}\", logger.Name);\n            }\n\n            var providerManager = ServiceLocator.Instance.Get<IProviderManager>();\n            foreach (var instance in providerManager.Instances)\n            {\n                Log.DebugFormat(\"Provider: {0}\", instance.Name);\n                Log.DebugFormat(\"   - is {0}active\", instance.IsActive ? string.Empty : \"not \");\n                Log.DebugFormat(\"   - logger = {0}\", instance.Logger);\n            }\n        }\n\n        private void ProcessCommandLine(IEnumerable<string> commandLine)\n        {\n            commandLine.ThrowIfNull(nameof(commandLine));\n\n            var commandLineArguments = commandLine as string[] ?? commandLine.ToArray();\n            if (!commandLineArguments.Any())\n            {\n                throw new ArgumentException(\"Collection must have at least one element\", nameof(commandLine));\n            }\n\n            var sessionManager = ServiceLocator.Instance.Get<ISessionManager>();\n\n            var unknownCommandLine = false;\n            var args = commandLineArguments.ToArray();\n\n            var options = Parser.Default.ParseArguments<Log4NetOptions, NLogOptions>(args).MapResult(\n                (Log4NetOptions o) => Tuple.Create<string, IOptions>(\"log4net\", o),\n                (NLogOptions o) => Tuple.Create<string, IOptions>(\"nlog\", o),\n                _ => Tuple.Create<string, IOptions>(string.Empty, null));\n\n            var verb = options.Item1;\n\n            if (string.IsNullOrWhiteSpace(options.Item1))\n            {\n                var filePath = commandLineArguments.FirstOrDefault();\n                if (!File.Exists(filePath) || Path.GetExtension(filePath)?.ToUpper() != \".SNTL\")\n                {\n                    unknownCommandLine = true;\n                }\n            }\n\n            if (unknownCommandLine)\n            {\n                // TODO: command line usage dialog\n                MessageBox.Show(\n                    \"File does not exist or is not a Sentinel session file.\",\n                    \"Sentinel\",\n                    MessageBoxButton.OK,\n                    MessageBoxImage.Error);\n                return;\n            }\n\n            RemoveBindingReferences();\n\n            switch (verb)\n            {\n                case \"nlog\":\n                    CreateDefaultNLogListener((NLogOptions)options.Item2, sessionManager);\n                    break;\n                case \"log4net\":\n                    CreateDefaultLog4NetListener((Log4NetOptions)options.Item2, sessionManager);\n                    break;\n                default:\n                    sessionManager.LoadSession(commandLineArguments.FirstOrDefault());\n                    break;\n            }\n\n            BindViewToViewModel();\n\n            var frame = ServiceLocator.Instance.Get<IWindowFrame>();\n\n            // Add to the tab control.\n            var newTab = new TabItem { Header = sessionManager.Name, Content = frame };\n            TabControl.Items.Add(newTab);\n            TabControl.SelectedItem = newTab;\n        }\n\n        private void CreateDefaultLog4NetListener(Log4NetOptions log4NetOptions, ISessionManager sessionManager)\n        {\n            var info = $\"Using log4net listener on Udp port {log4NetOptions.Port}\";\n            Log.Debug(info);\n\n            var providerSettings = new UdpAppenderSettings\n            {\n                Port = log4NetOptions.Port,\n                Name = info,\n                Info = Log4NetProvider.ProviderRegistrationInformation.Info,\n            };\n\n            var providers =\n                Enumerable.Repeat(\n                    new PendingProviderRecord\n                    {\n                        Info = Log4NetProvider.ProviderRegistrationInformation.Info,\n                        Settings = providerSettings,\n                    },\n                    1);\n\n            sessionManager.LoadProviders(providers);\n        }\n\n        private void CreateDefaultNLogListener(NLogOptions verbOptions, ISessionManager sessionManager)\n        {\n            var name = $\"Using nlog listener on {(verbOptions.IsUdp ? \"Udp\" : \"Tcp\")} port {verbOptions.Port}\";\n            var info = NLogViewerProvider.ProviderRegistrationInformation.Info;\n            Log.Debug(name);\n\n            var providerSettings = new NetworkSettings\n            {\n                Protocol =\n                    verbOptions.IsUdp\n                        ? NetworkProtocol.Udp\n                        : NetworkProtocol.Tcp,\n                Port = verbOptions.Port,\n                Name = name,\n                Info = info,\n            };\n            var providers = Enumerable.Repeat(\n                new PendingProviderRecord { Info = info, Settings = providerSettings },\n                1);\n\n            sessionManager.LoadProviders(providers);\n        }\n\n        private void RestoreWindowPosition(WindowPlacementInfo wp)\n        {\n            _ = wp ?? throw new ArgumentNullException(nameof(wp));\n\n            // Validation routine will cope with Null being passed and if it finds an error, it returns null.\n            wp = ValidateScreenPosition(wp);\n\n            if (wp != null)\n            {\n                Log.DebugFormat(\n                    \"Window position being restored to ({0},{1})-({2},{3}) {4}\",\n                    wp.Top,\n                    wp.Left,\n                    wp.Top + wp.Height,\n                    wp.Left + wp.Width,\n                    wp.WindowState);\n\n                Top = wp.Top;\n                Left = wp.Left;\n                Width = wp.Width;\n                Height = wp.Height;\n\n                // TODO: would it make sense to start up minimized if that was how it was terminated?\n                WindowState = wp.WindowState;\n            }\n        }\n\n        private void PreferencesChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (Preferences != null)\n            {\n                if (e.PropertyName == \"Show\")\n                {\n                    if (Preferences.Show)\n                    {\n                        preferencesWindow = new PreferencesWindow(preferencesWindowTabSelected) { Owner = this };\n                        preferencesWindow.Show();\n                    }\n                    else if (preferencesWindow != null)\n                    {\n                        preferencesWindow.Close();\n                        preferencesWindow = null;\n                    }\n                }\n            }\n        }\n\n        private void ViewManagerChanged(object sender, NotifyCollectionChangedEventArgs e)\n        {\n            if (e.Action == NotifyCollectionChangedAction.Add)\n            {\n                TabControl.SelectedIndex = TabControl.Items.Count - 1;\n            }\n        }\n\n        private void OnClosed(object sender, CancelEventArgs e)\n        {\n            var windowInfo = new WindowPlacementInfo\n            {\n                Height = (int)Height,\n                Top = (int)Top,\n                Left = (int)Left,\n                Width = (int)Width,\n                WindowState = WindowState,\n            };\n\n            var filename = Path.ChangeExtension(persistingFilename, \".json\");\n            PersistingSettings.Save(filename, windowInfo, Preferences);\n\n            var recentFileInfo = new RecentFileInfo { RecentFilePaths = RecentFiles.ToList() };\n\n            JsonHelper.SerializeToFile(recentFileInfo, Path.ChangeExtension(persistingRecentFileName, \".json\"));\n        }\n\n        private void RetainOnlyStandardFilters(object sender, FilterEventArgs e)\n        {\n            sender.ThrowIfNull(nameof(sender));\n            e.ThrowIfNull(nameof(e));\n            e.Accepted = e.Item is IStandardDebuggingFilter;\n        }\n\n        private void ExcludeStandardFilters(object sender, FilterEventArgs e)\n        {\n            sender.ThrowIfNull(nameof(sender));\n            e.ThrowIfNull(nameof(e));\n            e.Accepted = !(e.Item is IStandardDebuggingFilter || e.Item is ISearchFilter);\n        }\n\n        private void RetainOnlyStandardHighlighters(object sender, FilterEventArgs e)\n        {\n            sender.ThrowIfNull(nameof(sender));\n            e.ThrowIfNull(nameof(e));\n            e.Accepted = e.Item is IStandardDebuggingHighlighter;\n        }\n\n        private void ExcludeStandardHighlighters(object sender, FilterEventArgs e)\n        {\n            sender.ThrowIfNull(nameof(sender));\n            e.ThrowIfNull(nameof(e));\n            e.Accepted = !(e.Item is IStandardDebuggingHighlighter);\n        }\n\n        private void SearchToggleButtonChecked(object sender, RoutedEventArgs e)\n        {\n            Debug.Assert(\n                sender.GetType() == typeof(RibbonToggleButton),\n                $\"A {sender.GetType()} accessed the wrong method\");\n\n            var button = (RibbonToggleButton)sender;\n            switch (button.Label)\n            {\n                case \"Highlight\":\n                    BindSearchToSearchHighlighter();\n                    break;\n                case \"Filter\":\n                    BindSearchToSearchFilter();\n                    break;\n                case \"Extract\":\n                    BindSearchToSearchExtractor();\n                    break;\n            }\n        }\n\n        private void BindSearchToSearchExtractor()\n        {\n            SearchRibbonTextBox.SetBinding(TextBox.TextProperty, CreateBinding(\"Pattern\", SearchExtractor));\n            SearchModeListBox.SetBinding(Selector.SelectedItemProperty, CreateBinding(\"Mode\", SearchExtractor));\n            SearchTargetComboBox.SetBinding(Selector.SelectedItemProperty, CreateBinding(\"Field\", SearchExtractor));\n\n            HighlightToggleButton.IsChecked = false;\n            FilterToggleButton.IsChecked = false;\n        }\n\n        private Binding CreateBinding(string path, object source)\n        {\n            return new Binding\n            {\n                Source = source,\n                Path = new PropertyPath(path),\n                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,\n            };\n        }\n\n        private void BindSearchToSearchFilter()\n        {\n            SearchRibbonTextBox.SetBinding(TextBox.TextProperty, CreateBinding(\"Pattern\", SearchFilter));\n            SearchModeListBox.SetBinding(Selector.SelectedItemProperty, CreateBinding(\"Mode\", SearchFilter));\n            SearchTargetComboBox.SetBinding(Selector.SelectedItemProperty, CreateBinding(\"Field\", SearchFilter));\n            HighlightToggleButton.IsChecked = false;\n            ExtractToggleButton.IsChecked = false;\n        }\n\n        private void BindSearchToSearchHighlighter()\n        {\n            SearchRibbonTextBox.SetBinding(TextBox.TextProperty, CreateBinding(\"Search\", Search));\n            SearchModeListBox.SetBinding(Selector.SelectedItemProperty, CreateBinding(\"Mode\", Search));\n            SearchTargetComboBox.SetBinding(Selector.SelectedItemProperty, CreateBinding(\"Field\", Search));\n            FilterToggleButton.IsChecked = false;\n            ExtractToggleButton.IsChecked = false;\n        }\n\n        private void RemoveBindingReferences()\n        {\n            var notifyPropertyChanged = Preferences as INotifyPropertyChanged;\n            if (notifyPropertyChanged != null)\n            {\n                notifyPropertyChanged.PropertyChanged -= PreferencesChanged;\n            }\n\n            ViewManager.Viewers.CollectionChanged -= ViewManagerChanged;\n        }\n\n        private void BindViewToViewModel()\n        {\n            // Append version number to caption (to save effort of producing an about screen)\n            Title =\n                $\"{Assembly.GetExecutingAssembly().GetName().Name} ({Assembly.GetExecutingAssembly().GetName().Version})\"\n                + $\" {ServiceLocator.Instance.Get<ISessionManager>().Name}\";\n\n            // Preferences, if initialised, has come from persistence, so register current copy\n            // otherwise, ask for one that will be shared.\n            if (Preferences != null)\n            {\n                ServiceLocator.Instance.Register<IUserPreferences>(Preferences);\n            }\n            else\n            {\n                Preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n            }\n\n            ViewManager = ServiceLocator.Instance.Get<IViewManager>();\n\n            // Maintaining column widths is proving difficult in Xaml alone, so\n            // add an observer here and deal with it in code.\n            if (Preferences is INotifyPropertyChanged changed)\n            {\n                changed.PropertyChanged += PreferencesChanged;\n            }\n\n            DataContext = this;\n\n            // When a new item is added, select the newest one.\n            ViewManager.Viewers.CollectionChanged += ViewManagerChanged;\n\n            // View-specific bindings\n            var collapseIfZero = new CollapseIfZeroConverter();\n\n            var standardHighlighters = new CollectionViewSource { Source = Highlighters.Highlighters };\n            standardHighlighters.View.Filter = c => c is IStandardDebuggingHighlighter;\n\n            var customHighlighters = new CollectionViewSource { Source = Highlighters.Highlighters };\n            customHighlighters.View.Filter = c => !(c is IStandardDebuggingHighlighter);\n\n            StandardHighlightersRibbonGroup.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = standardHighlighters });\n\n            StandardHighlighterRibbonGroupOnTab.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = standardHighlighters });\n            var collapsingStandardHighlightersBinding = new Binding\n            {\n                Source = standardHighlighters,\n                Path = new PropertyPath(\"Count\"),\n                Converter = collapseIfZero,\n            };\n            StandardHighlighterRibbonGroupOnTab.SetBinding(VisibilityProperty, collapsingStandardHighlightersBinding);\n\n            CustomHighlighterRibbonGroupOnTab.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = customHighlighters });\n\n            var collapsingCustomHighlightersBinding = new Binding\n            {\n                Source = customHighlighters,\n                Path = new PropertyPath(\"Count\"),\n                Converter = collapseIfZero,\n            };\n            CustomHighlighterRibbonGroupOnTab.SetBinding(VisibilityProperty, collapsingCustomHighlightersBinding);\n\n            var standardFilters = new CollectionViewSource { Source = Filters.Filters };\n            standardFilters.View.Filter = c => c is IStandardDebuggingFilter;\n\n            var customFilters = new CollectionViewSource { Source = Filters.Filters };\n            customFilters.View.Filter = c => !(c is IStandardDebuggingFilter);\n\n            StandardFiltersRibbonGroup.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = standardFilters });\n\n            StandardFiltersRibbonGroupOnTab.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = standardFilters });\n\n            StandardFiltersRibbonGroupOnTab.SetBinding(\n                VisibilityProperty,\n                new Binding { Source = standardFilters, Path = new PropertyPath(\"Count\"), Converter = collapseIfZero });\n            CustomFiltersRibbonGroupOnTab.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = customFilters });\n            CustomFiltersRibbonGroupOnTab.SetBinding(\n                VisibilityProperty,\n                new Binding { Source = customFilters, Path = new PropertyPath(\"Count\"), Converter = collapseIfZero });\n\n            var customExtractors = Extractors.Extractors;\n            CustomExtractorsRibbonGroupOnTab.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = customExtractors });\n\n            var customClassifyiers = ClassifyingService.Classifiers;\n            CustomClassifiersRibbonGroupOnTab.SetBinding(\n                ItemsControl.ItemsSourceProperty,\n                new Binding { Source = customClassifyiers });\n\n            BindToSearchElements();\n\n            // Column view buttons\n            ExceptionRibbonToggleButton.SetBinding(\n                ToggleButton.IsCheckedProperty,\n                new Binding { Source = Preferences, Path = new PropertyPath(\"ShowExceptionColumn\") });\n            ThreadRibbonToggleButton.SetBinding(\n                ToggleButton.IsCheckedProperty,\n                new Binding { Source = Preferences, Path = new PropertyPath(\"ShowThreadColumn\") });\n            SourceHostRibbonToggleButton.SetBinding(\n                ToggleButton.IsCheckedProperty,\n                new Binding { Source = Preferences, Path = new PropertyPath(\"ShowSourceColumn\") });\n            DebugSourceRibbonToggleButton.SetBinding(\n                ToggleButton.IsCheckedProperty,\n                new Binding { Source = Preferences, Path = new PropertyPath(\"ShowSourceInformationColumns\") });\n            ContextRibbonToggleButton.SetBinding(\n                ToggleButton.IsCheckedProperty,\n                new Binding { Source = Preferences, Path = new PropertyPath(\"ShowContextColumn\") });\n        }\n\n        private void BindToSearchElements()\n        {\n            // Bind search\n            HighlightToggleButton.SetBinding(ToggleButton.IsCheckedProperty, CreateBinding(\"Enabled\", Search));\n            FilterToggleButton.SetBinding(ToggleButton.IsCheckedProperty, CreateBinding(\"Enabled\", SearchFilter));\n            ExtractToggleButton.SetBinding(ToggleButton.IsCheckedProperty, CreateBinding(\"Enabled\", SearchExtractor));\n\n            if (Search.Enabled)\n            {\n                BindSearchToSearchHighlighter();\n            }\n            else if (SearchFilter.Enabled)\n            {\n                BindSearchToSearchFilter();\n            }\n            else if (SearchExtractor.Enabled)\n            {\n                BindSearchToSearchExtractor();\n            }\n        }\n\n        private void GetRecentlyOpenedFiles()\n        {\n            if (string.IsNullOrWhiteSpace(persistingRecentFileName))\n            {\n                return;\n            }\n\n            var fileName = Path.ChangeExtension(persistingRecentFileName, \".json\");\n            var recentFileInfo = JsonHelper.DeserializeFromFile<RecentFileInfo>(fileName);\n\n            recentFilePathList = recentFileInfo?.RecentFilePaths.ToList() ?? new List<string>();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Controls/PersistingSettings.cs",
    "content": "﻿namespace Sentinel.Controls\r\n{\r\n    using System;\r\n    using System.Diagnostics;\r\n    using System.Runtime.Serialization;\r\n    using Sentinel.Interfaces;\r\n    using Sentinel.Support;\r\n\r\n    [DataContract]\r\n    public class PersistingSettings\r\n    {\r\n        [DataMember]\r\n        public WindowPlacementInfo WindowPlacementInfo { get; set; }\r\n\r\n        [DataMember]\r\n        public IUserPreferences UserPreferences { get; set; }\r\n\r\n        internal static PersistingSettings Load(string fileName)\r\n        {\r\n            if (string.IsNullOrWhiteSpace(fileName))\r\n            {\r\n                throw new System.ArgumentException(\"Value cannot be null or whitespace.\", nameof(fileName));\r\n            }\r\n\r\n            // Note that PersistingSettings is a new file format, so need to detect whether using\r\n            // new serialisation or upgrading.\r\n            if (System.IO.File.Exists(fileName))\r\n            {\r\n                var fileContents = System.IO.File.ReadAllText(fileName);\r\n\r\n                // Version detect from file-header signature:\r\n                var fileHeader = fileContents\r\n                    .Substring(0, 52)\r\n                    .Replace(\" \", string.Empty)\r\n                    .Replace(\"\\r\", string.Empty)\r\n                    .Replace(\"\\n\", string.Empty);\r\n\r\n                switch (fileHeader)\r\n                {\r\n                    case \"{\\\"$type\\\":\\\"Sentinel.Controls.WindowPlacementInfo\":\r\n                        return DeserializeFromV1(fileContents);\r\n                    case \"{\\\"$type\\\":\\\"Sentinel.Controls.PersistingSettings,\":\r\n                        return DeserializeFromV2(fileContents);\r\n                    default:\r\n                        return null;\r\n                }\r\n            }\r\n\r\n            return null;\r\n        }\r\n\r\n        internal static void Save(string fileName, WindowPlacementInfo placementInfo, IUserPreferences userPreferences)\r\n        {\r\n            if (string.IsNullOrWhiteSpace(fileName))\r\n            {\r\n                throw new ArgumentException(\"Value cannot be null or whitespace.\", nameof(fileName));\r\n            }\r\n\r\n            var wrapper = new PersistingSettings\r\n            {\r\n                WindowPlacementInfo = placementInfo,\r\n                UserPreferences = userPreferences,\r\n            };\r\n\r\n            try\r\n            {\r\n                JsonHelper.SerializeToFile(wrapper, fileName);\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                Trace.TraceError($\"Unable to write persistence file: {e.Message}\");\r\n            }\r\n        }\r\n\r\n        private static PersistingSettings DeserializeFromV1(string fileContents)\r\n        {\r\n            Trace.WriteLine(\"DeserializeFromV1\");\r\n\r\n            PersistingSettings wrapper = null;\r\n\r\n            try\r\n            {\r\n                wrapper = new PersistingSettings\r\n                {\r\n                    WindowPlacementInfo = JsonHelper.DeserializeFromString<WindowPlacementInfo>(fileContents),\r\n                };\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                Trace.TraceError($\"Deserialize error: {e.Message}\");\r\n            }\r\n\r\n            return wrapper;\r\n        }\r\n\r\n        private static PersistingSettings DeserializeFromV2(string fileContents)\r\n        {\r\n            Trace.WriteLine(\"DeserializeFromV1\");\r\n\r\n            try\r\n            {\r\n                return JsonHelper.DeserializeFromString<PersistingSettings>(fileContents);\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                Trace.TraceError($\"Deserialize error: {e.Message}\");\r\n                return null;\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Controls/PreferencesControl.xaml",
    "content": "﻿<UserControl xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:controls=\"clr-namespace:Sentinel.Controls\"\n             xmlns:highlighters=\"clr-namespace:Sentinel.Highlighters.Gui\"\n             xmlns:classificationControls=\"clr-namespace:Sentinel.Classification.Gui\"\n             xmlns:filters=\"clr-namespace:Sentinel.Filters.Gui\"\n             xmlns:extractors=\"clr-namespace:Sentinel.Extractors.Gui\"\n             xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n             mc:Ignorable=\"d\"\n             x:Class=\"Sentinel.Controls.PreferencesControl\"\n             x:Name=\"UserControl\"\n             Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\">\n\n    <UserControl.Resources>\n        <converters:BooleanInvertingValueConverter x:Key=\"BooleanInverterConverter\" />\n    </UserControl.Resources>\n\n    <Grid x:Name=\"LayoutRoot\">\n        <TabControl SelectedIndex=\"{Binding SelectedTabIndex}\">\n            <TabItem Header=\"General\">\n                <ScrollViewer HorizontalScrollBarVisibility=\"Hidden\"\n                              VerticalScrollBarVisibility=\"Auto\">\n                    <DockPanel Margin=\"5\">\n                        <StackPanel DockPanel.Dock=\"Top\">\n                            <GroupBox Header=\"Column options\">\n                                <Grid Margin=\"5\">\n                                    <Grid.RowDefinitions>\n                                        <RowDefinition />\n                                        <RowDefinition />\n                                        <RowDefinition />\n                                        <RowDefinition />\n                                        <RowDefinition />\n                                    </Grid.RowDefinitions>\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition Width=\"Auto\" />\n                                        <ColumnDefinition Width=\"5\" />\n                                        <ColumnDefinition Width=\"*\" />\n                                    </Grid.ColumnDefinitions>\n\n                                    <TextBlock Grid.Row=\"0\"\n                                               Grid.Column=\"0\"\n                                               VerticalAlignment=\"Center\"\n                                               Text=\"Issue Types column :\" />\n                                    <ComboBox Grid.Row=\"0\"\n                                              Grid.Column=\"2\"\n                                              SelectedIndex=\"{Binding Preferences.SelectedTypeOption}\"\n                                              Width=\"Auto\"\n                                              HorizontalAlignment=\"Left\"\n                                              Margin=\"0,5,0,5\"\n                                              ItemsSource=\"{Binding Preferences.TypeOptions}\"\n                                              MinWidth=\"100\" />\n                                    <TextBlock Grid.Row=\"1\"\n                                               Grid.Column=\"0\"\n                                               VerticalAlignment=\"Center\"\n                                               Text=\"Date Format :\" />\n                                    <ComboBox Grid.Row=\"1\"\n                                              Grid.Column=\"2\"\n                                              SelectedIndex=\"{Binding Preferences.SelectedDateOption}\"\n                                              HorizontalAlignment=\"Left\"\n                                              Margin=\"0,5,0,5\"\n                                              ItemsSource=\"{Binding Preferences.DateFormatOptions}\"\n                                              MinWidth=\"100\" />\n                                    <TextBlock Grid.Row=\"2\"\n                                               Grid.Column=\"0\"\n                                               Margin=\"0,8\"\n                                               VerticalAlignment=\"Top\"\n                                               Text=\"Time Format :\" />\n                                    <StackPanel Grid.Row=\"2\"\n                                                Grid.Column=\"2\"\n                                                Orientation=\"Vertical\">\n                                        <ComboBox SelectedIndex=\"{Binding Preferences.SelectedTimeFormatOption}\"\n                                                  Margin=\"0,5,0,5\"\n                                                  HorizontalAlignment=\"Left\"\n                                                  ItemsSource=\"{Binding Preferences.TimeFormatOptions}\"\n                                                  MinWidth=\"100\" />\n                                        <CheckBox Margin=\"0,5,0,5\"\n                                                  IsChecked=\"{Binding Preferences.ConvertUtcTimesToLocalTimeZone}\">\n                                            Convert UTC times to local timezone\n                                        </CheckBox>\n                                    </StackPanel>\n                                    <TextBlock Grid.Row=\"3\"\n                                               Grid.Column=\"0\"\n                                               Margin=\"0,4\"\n                                               VerticalAlignment=\"Top\"\n                                               Text=\"Message Date/Time value :\" />\n                                    <StackPanel Grid.Row=\"3\"\n                                                Grid.Column=\"2\">\n                                        <RadioButton Margin=\"0,5,0,5\"\n                                                     IsChecked=\"{Binding Preferences.UseArrivalDateTime}\">\n                                            Use time of message receipt by Sentinel\n                                        </RadioButton>\n                                        <RadioButton Margin=\"0,0,0,5\"\n                                                     IsChecked=\"{Binding Preferences.UseArrivalDateTime, Converter={StaticResource BooleanInverterConverter}}\">\n                                            Use sender's time\n                                        </RadioButton>\n                                    </StackPanel>\n                                    <StackPanel Orientation=\"Vertical\"\n                                                Grid.Row=\"4\"\n                                                Margin=\"0,5,0,5\"\n                                                Grid.ColumnSpan=\"3\"\n                                                Grid.Column=\"0\">\n                                        <CheckBox IsChecked=\"{Binding Preferences.ShowExceptionColumn}\"\n                                                  HorizontalAlignment=\"Left\"\n                                                  MinWidth=\"100\"\n                                                  Margin=\"0,0,0,5\"\n                                                  ToolTip=\"Showing exceptions will become useful once it is fully implemented.\"\n                                                  Content=\"Show the Exception column\" />\n                                        <CheckBox IsChecked=\"{Binding Preferences.ShowThreadColumn}\"\n                                                  HorizontalAlignment=\"Left\"\n                                                  MinWidth=\"100\"\n                                                  Margin=\"0,0,0,5\"\n                                                  ToolTip=\"Showing the originating thread may not always be useful.  The thread identifier (a number) is unique only to the original application.  This can be misleading when multiple applications are being logged, additionally, a correctly named system will yield a more useful context.\"\n                                                  Content=\"Show the Thread column\" />\n                                        <CheckBox IsChecked=\"{Binding Preferences.ShowSourceColumn}\"\n                                                  HorizontalAlignment=\"Left\"\n                                                  MinWidth=\"100\"\n                                                  Margin=\"0,0,0,5\"\n                                                  ToolTip=\"Show the name of the machine from which the message was sourced\"\n                                                  Content=\"Show the message source host-name column\" />\n                                        <CheckBox IsChecked=\"{Binding Preferences.ShowSourceInformationColumns}\"\n                                                  HorizontalAlignment=\"Left\"\n                                                  Margin=\"0,0,0,5\"\n                                                  ToolTip=\"Shows/hides the columns with the source-code information (nLog only)\"\n                                                  Content=\"Show source debugging information\" />\n                                        <StackPanel Orientation=\"Horizontal\"\n                                                    VerticalAlignment=\"Center\">\n                                            <CheckBox IsChecked=\"{Binding Preferences.ShowContextColumn}\"\n                                                      HorizontalAlignment=\"Left\"\n                                                      VerticalAlignment=\"Bottom\"\n                                                      Margin=\"0,0,0,5\"\n                                                      ToolTip=\"Shows/hides the context column (nLog only)\"\n                                                      Content=\"Show the context column\" />\n                                            <Label Margin=\"20,0,0,0\">Property for context:</Label>\n                                            <TextBox\n                                                Text=\"{Binding Preferences.ContextProperty, UpdateSourceTrigger=PropertyChanged}\"\n                                                Margin=\"10,0,0,0\"\n                                                Width=\"200\"\n                                                VerticalAlignment=\"Center\" />\n                                        </StackPanel>\n                                        <CheckBox IsChecked=\"{Binding Preferences.UseLazyRebuild}\"\n                                                  HorizontalAlignment=\"Left\"\n                                                  ToolTip=\"Enables/disables live resorting of sortable columns\"\n                                                  Content=\"Enable live column resorting mode\" />\n                                    </StackPanel>\n                                </Grid>\n                            </GroupBox>\n                            <GroupBox Header=\"Row options\">\n                                <CheckBox Margin=\"5\"\n                                          Content=\"Use tighter row separation on Vista / Windows7\"\n                                          ToolTip=\"Vista and Windows 7 put spacing around each row that takes up a fair amount of space, enabling this option tries to reduce the space to something like that used by Windows XP\"\n                                          IsChecked=\"{Binding Preferences.UseTighterRows}\" />\n                            </GroupBox>\n                            <GroupBox Header=\"Message limits\">\n                                <StackPanel Orientation=\"Horizontal\"\n                                            Margin=\"5\"\n                                            VerticalAlignment=\"Center\">\n                                    <CheckBox IsChecked=\"{Binding Preferences.LimitMessages}\"\n                                              HorizontalAlignment=\"Left\"\n                                              VerticalAlignment=\"Bottom\"\n                                              Margin=\"0,0,0,5\"\n                                              Content=\"Limit the maximum number of messages retained\" />\n                                    <StackPanel IsEnabled=\"{Binding Preferences.LimitMessages}\"\n                                                Orientation=\"Horizontal\">\n                                        <Label Margin=\"20,0,0,0\">Message count:</Label>\n                                        <controls:IntegerTextBox\n                                            Text=\"{Binding Preferences.MaximumMessageCount, UpdateSourceTrigger=PropertyChanged}\"\n                                            Margin=\"10,0,0,0\"\n                                            Width=\"100\"\n                                            VerticalAlignment=\"Center\" />\n                                    </StackPanel>\n                                </StackPanel>\n                            </GroupBox>\n                            <GroupBox Header=\"Layout\">\n                                <CheckBox Margin=\"5\"\n                                          Content=\"Use Stacked Layout\"\n                                          ToolTip=\"Orientate the two windows as stacked rather than side-by-side.\"\n                                          IsChecked=\"{Binding Preferences.UseStackedLayout}\" />\n                            </GroupBox>\n                            <GroupBox Header=\"Interactions\">\n                                <CheckBox Margin=\"5\"\n                                          Content=\"Show exceptions panel on double-click\"\n                                          ToolTip=\"When a log entry has additional exception information, double-clicking will show the exception detail panel.\"\n                                          IsChecked=\"{Binding Preferences.DoubleClickToShowExceptions}\" />\n                            </GroupBox>\n                        </StackPanel>\n                    </DockPanel>\n                </ScrollViewer>\n            </TabItem>\n            <TabItem Header=\"Classifiers\">\n                <GroupBox Header=\"Available Classifiers\"\n                          Margin=\"5\">\n                    <DockPanel>\n                        <StackPanel Margin=\"5\"\n                                    DockPanel.Dock=\"Top\">\n                            <TextBlock TextWrapping=\"WrapWithOverflow\">\n                                <Run Text=\"Classifiers change the type of log messages when enabled.\" />\n                                <Run\n                                    Text=\"Messages are classified as they are received and cannot be changed afterwards.\" />\n                            </TextBlock>\n                            <TextBlock TextWrapping=\"WrapWithOverflow\"\n                                       Margin=\"0,5,0,5\"\n                                       Text=\"The classifiers are evaluated in the order shown below and the last enabled match wins:\" />\n                        </StackPanel>\n                        <classificationControls:ClassificationsControl />\n                    </DockPanel>\n                </GroupBox>\n            </TabItem>\n            <TabItem Header=\"Highlighters\">\n                <GroupBox Header=\"Available Highlighters\"\n                          Margin=\"5\">\n                    <DockPanel>\n                        <StackPanel Margin=\"5\"\n                                    DockPanel.Dock=\"Top\">\n                            <TextBlock TextWrapping=\"WrapWithOverflow\">\n                                <Run Text=\"Highlighters change the appearance of matching log messages when enabled\" />\n                                <Run Text=\"so that certain messages are made to stand out from others.\" />\n                                <Run Text=\"An example would be marking Error messages in a red background.\" />\n                            </TextBlock>\n                            <TextBlock TextWrapping=\"WrapWithOverflow\"\n                                       Margin=\"0,5,0,5\"\n                                       Text=\"The highlighters are evaluated in the order shown below and the first enabled match wins:\" />\n                        </StackPanel>\n                        <highlighters:HighlightersControl />\n                    </DockPanel>\n                </GroupBox>\n            </TabItem>\n            <TabItem Header=\"Filters\">\n                <GroupBox Header=\"Available Filters\"\n                          Margin=\"5\">\n                    <DockPanel>\n                        <StackPanel Margin=\"5\"\n                                    DockPanel.Dock=\"Top\">\n                            <TextBlock TextWrapping=\"WrapWithOverflow\">\n                                <Run Text=\"Filters remove matching log messages from view when enabled.\" />\n                                <Run\n                                    Text=\"They do not prevent the messages from being recorded, just from being displayed.\" />\n                            </TextBlock>\n                            <TextBlock TextWrapping=\"WrapWithOverflow\"\n                                       Margin=\"0,5,0,5\"\n                                       Text=\"The list below indicates the available filters that may be applied:\" />\n                        </StackPanel>\n                        <filters:FiltersControl Height=\"Auto\"\n                                                VerticalAlignment=\"Stretch\" />\n                    </DockPanel>\n                </GroupBox>\n            </TabItem>\n            <TabItem Header=\"Extractors\">\n                <GroupBox Header=\"Available Extractors\"\n                          Margin=\"5\">\n                    <DockPanel>\n                        <StackPanel Margin=\"5\"\n                                    DockPanel.Dock=\"Top\">\n                            <TextBlock TextWrapping=\"WrapWithOverflow\">\n                                <Run Text=\"Extractors remove unmatching log messages from view when enabled.\" />\n                                <Run\n                                    Text=\"They do not prevent the messages from being recorded, just from being displayed.\" />\n                            </TextBlock>\n                            <TextBlock TextWrapping=\"WrapWithOverflow\"\n                                       Margin=\"0,5,0,5\"\n                                       Text=\"The list below indicates the available extractors that may be applied:\" />\n                        </StackPanel>\n                        <extractors:ExtractorsControl Height=\"Auto\"\n                                                      VerticalAlignment=\"Stretch\" />\n                    </DockPanel>\n                </GroupBox>\n            </TabItem>\n            <TabItem Header=\"Images\">\n                <GroupBox Header=\"Available Images\"\n                          Margin=\"5\">\n                    <DockPanel>\n                        <StackPanel Margin=\"5\"\n                                    DockPanel.Dock=\"Top\">\n                            <TextBlock TextWrapping=\"WrapWithOverflow\">\n                                <Run Text=\"Images are displayed for log messages by matching types.\" />\n                                <Run Text=\"They are displayed for Classifiers, Highlighters, Filters, and Extractors\" />\n                                <Run Text=\"by matching names.\" />\n                            </TextBlock>\n                            <TextBlock TextWrapping=\"WrapWithOverflow\"\n                                       Margin=\"0,5,0,5\"\n                                       Text=\"The list below indicates the registered images:\" />\n                        </StackPanel>\n                        <controls:ImageTypesControl />\n                    </DockPanel>\n                </GroupBox>\n            </TabItem>\n            <TabItem Header=\"Commands\">\n                <GroupBox Header=\"Experimental Commands\"\n                          Margin=\"5\">\n                    <DockPanel>\n                        <StackPanel Margin=\"5\"\n                                    DockPanel.Dock=\"Top\">\n                            <TextBlock TextWrapping=\"WrapWithOverflow\">\n                                <Run\n                                    Text=\"When specific logging messages are sent, interpret them as commands for controlling Sentinel.\" />\n                            </TextBlock>\n                            <Grid>\n                                <Grid.ColumnDefinitions>\n                                    <ColumnDefinition Width=\"*\" />\n                                    <ColumnDefinition Width=\"Auto\" />\n                                    <ColumnDefinition Width=\"200\" />\n                                </Grid.ColumnDefinitions>\n\n                                <CheckBox Content=\"Clear the log\"\n                                          VerticalAlignment=\"Center\"\n                                          IsChecked=\"{Binding Preferences.EnableClearCommand}\" />\n                                <Label Grid.Column=\"1\">Text to match:</Label>\n                                <TextBox Grid.Column=\"2\"\n                                         VerticalAlignment=\"Center\"\n                                         Text=\"{Binding Preferences.ClearCommandMatchText}\" />\n                            </Grid>\n                        </StackPanel>\n                    </DockPanel>\n                </GroupBox>\n            </TabItem>\n        </TabControl>\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/Controls/PreferencesControl.xaml.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    /// <summary>\n    /// Interaction logic for PreferencesControl.xaml.\n    /// </summary>\n    public partial class PreferencesControl\n    {\n        public PreferencesControl()\n        {\n            InitializeComponent();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Controls/PreferencesWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Controls.PreferencesWindow\"\r\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n        xmlns:Controls=\"clr-namespace:Sentinel.Controls\"\r\n        Title=\"Preferences\"\r\n        Height=\"548.644\"\r\n        Width=\"662\"\r\n        ResizeMode=\"CanResizeWithGrip\"\r\n        ShowInTaskbar=\"False\"\r\n        Closed=\"WindowClosed\"\r\n        Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\"\r\n        WindowStyle=\"ToolWindow\"\r\n        WindowStartupLocation=\"CenterOwner\">\r\n    <Controls:PreferencesControl HorizontalAlignment=\"Stretch\"\r\n                              VerticalAlignment=\"Stretch\" />\r\n</Window>\r\n"
  },
  {
    "path": "Sentinel/Controls/PreferencesWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    using System;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for PreferencesWindow.xaml.\n    /// </summary>\n    public partial class PreferencesWindow\n    {\n        public PreferencesWindow()\n            : this(0)\n        {\n        }\n\n        public PreferencesWindow(int selectedTabIndex)\n        {\n            InitializeComponent();\n            Preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n            SelectedTabIndex = selectedTabIndex;\n            DataContext = this;\n        }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public int SelectedTabIndex { get; set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public IUserPreferences Preferences { get; private set; }\n\n        private void WindowClosed(object sender, EventArgs e)\n        {\n            Preferences.Show = false;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Controls/RecentFileInfo.cs",
    "content": "﻿namespace Sentinel.Controls\n{\n    using System.Collections.Generic;\n    using System.Runtime.Serialization;\n\n    [DataContract]\n    public class RecentFileInfo\n    {\n        [DataMember]\n        public IEnumerable<string> RecentFilePaths { get; set; }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Controls/WindowPlacementInfo.cs",
    "content": "﻿namespace Sentinel.Controls\r\n{\r\n    using System.Runtime.Serialization;\r\n    using System.Windows;\r\n\r\n    [DataContract]\r\n    public class WindowPlacementInfo\r\n    {\r\n        [DataMember]\r\n        public int Top { get; set; }\r\n\r\n        [DataMember]\r\n        public int Left { get; set; }\r\n\r\n        [DataMember]\r\n        public int Width { get; set; }\r\n\r\n        [DataMember]\r\n        public int Height { get; set; }\r\n\r\n        [DataMember]\r\n        public WindowState WindowState { get; set; }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/EventLogMonitor/CommandLineOptions.cs",
    "content": "﻿namespace Sentinel.EventLogMonitor\n{\n    using CommandLine;\n\n    public class CommandLineOptions\n    {\n        [Option('b', \"no-banner\", Default = false, HelpText = \"Hide the copyright banner shown on application startup\")]\n        public bool SuppressBanner { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/EventLogMonitor/EventLogEntry.cs",
    "content": "﻿namespace Sentinel.EventLogMonitor\n{\n    using System;\n    using System.Diagnostics;\n    using System.Text;\n\n    using Sentinel.EventLogMonitor.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    internal class EventLogEntry : IEventLogEntry\n    {\n        public EventLogEntry(System.Diagnostics.EventLogEntry entry)\n        {\n            entry.ThrowIfNull(nameof(entry));\n            Entry = entry;\n        }\n\n        public string MachineName => Entry.MachineName;\n\n        public EventLogEntryType EntryType => Entry.EntryType;\n\n        public string Message => Entry.Message;\n\n        public DateTime EventTime => Entry.TimeGenerated;\n\n        public string Category => Entry.Category;\n\n        public long InstanceId => Entry.InstanceId;\n\n        public string Source => Entry.Source;\n\n        public string UserName => Entry.UserName;\n\n        private System.Diagnostics.EventLogEntry Entry { get; }\n\n        public override string ToString()\n        {\n            var sb = new StringBuilder();\n            sb.AppendLine($\"Type    : {EntryType}\");\n            sb.AppendLine($\"Created : {EventTime}\");\n            sb.AppendLine($\"Category: {Category}\");\n            sb.AppendLine($\"Message : {Message}\");\n            sb.AppendLine($\"Machine : {MachineName}\");\n            sb.AppendLine($\"Source  : {Source}\");\n            sb.AppendLine($\"User    : {UserName}\");\n            sb.AppendLine($\"Inst Id : {InstanceId}\");\n\n            return sb.ToString();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/EventLogMonitor/Interfaces/IEventLogEntry.cs",
    "content": "﻿namespace Sentinel.EventLogMonitor.Interfaces\n{\n    using System;\n    using System.Diagnostics;\n\n    using Newtonsoft.Json;\n\n    public interface IEventLogEntry\n    {\n        [JsonProperty(Order = 1, Required = Required.Always)]\n        EventLogEntryType EntryType { get; }\n\n        [JsonProperty(Order = 2)]\n        string Message { get; }\n\n        [JsonProperty(Order = 3)]\n        string Source { get; }\n\n        [JsonProperty(Order = 4)]\n        DateTime EventTime { get; }\n\n        [JsonProperty(Order = 5)]\n        string Category { get; }\n\n        [JsonProperty(Order = 6, DefaultValueHandling = DefaultValueHandling.Ignore)]\n        long InstanceId { get; }\n\n        [JsonProperty(Order = 7, DefaultValueHandling = DefaultValueHandling.Ignore)]\n        string MachineName { get; }\n\n        [JsonProperty(Order = 8, DefaultValueHandling = DefaultValueHandling.Ignore)]\n        string UserName { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/ExtractingService.cs",
    "content": "﻿namespace Sentinel.Extractors\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n\n    using Sentinel.Extractors.Gui;\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Services;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class ExtractingService<T> : ViewModelBase, IExtractingService<T>, IDefaultInitialisation\n        where T : class, IExtractor\n    {\n        private readonly CollectionChangeHelper<T> collectionHelper = new CollectionChangeHelper<T>();\n\n        private readonly IAddExtractorService addExtractorService = new AddExtractor();\n\n        private readonly IEditExtractorService editExtractorService = new EditExtractor();\n\n        private readonly IRemoveExtractorService removeExtractorService = new RemoveExtractor();\n\n        private int selectedIndex = -1;\n\n        public ExtractingService()\n        {\n            Add = new DelegateCommand(AddExtractor);\n            Edit = new DelegateCommand(EditExtractor, e => selectedIndex != -1);\n            Remove = new DelegateCommand(RemoveExtractor, e => selectedIndex != -1);\n\n            Extractors = new ObservableCollection<T>();\n            SearchExtractors = new ObservableCollection<T>();\n\n            // Register self as an observer of the collection.\n            collectionHelper.OnPropertyChanged += CustomExtractorPropertyChanged;\n\n            collectionHelper.ManagerName = \"ExtractingService\";\n            collectionHelper.NameLookup += e => e.Name;\n\n            Extractors.CollectionChanged += collectionHelper.AttachDetach;\n            SearchExtractors.CollectionChanged += collectionHelper.AttachDetach;\n\n            var searchExtractor = ServiceLocator.Instance.Get<ISearchExtractor>();\n\n            if (searchExtractor != null)\n            {\n                SearchExtractors.Add(searchExtractor as T);\n            }\n            else\n            {\n                Debug.Fail(\"The search extractor is null.\");\n            }\n        }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Add { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Edit { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public ICommand Remove { get; private set; }\n\n        // ReSharper disable once MemberCanBePrivate.Global\n        public int SelectedIndex\n        {\n            get\n            {\n                return selectedIndex;\n            }\n\n            set\n            {\n                if (value != selectedIndex)\n                {\n                    selectedIndex = value;\n                    OnPropertyChanged(nameof(SelectedIndex));\n                }\n            }\n        }\n\n        public ObservableCollection<T> Extractors { get; set; }\n\n        public ObservableCollection<T> SearchExtractors { get; set; }\n\n        public void Initialise()\n        {\n            // For adding standard extractors\n        }\n\n        public bool IsFiltered(ILogEntry entry)\n        {\n            return Extractors.Any(filter => filter.Enabled && filter.IsMatch(entry))\n                   || SearchExtractors.Any(filter => filter.Enabled && filter.IsMatch(entry));\n        }\n\n        private void AddExtractor(object obj)\n        {\n            addExtractorService.Add();\n        }\n\n        private void CustomExtractorPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            var extractor = sender as Extractor;\n            if (extractor != null)\n            {\n                Trace.WriteLine(\n                    $\"ExtractingService saw some activity on {extractor.Name} (IsEnabled = {extractor.Enabled})\");\n            }\n\n            OnPropertyChanged(string.Empty);\n        }\n\n        private void EditExtractor(object obj)\n        {\n            var extractor = Extractors.ElementAt(SelectedIndex);\n            if (extractor != null)\n            {\n                editExtractorService.Edit(extractor);\n            }\n        }\n\n        private void RemoveExtractor(object obj)\n        {\n            var extractor = Extractors.ElementAt(SelectedIndex);\n            removeExtractorService.Remove(extractor);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Extractor.cs",
    "content": "﻿namespace Sentinel.Extractors\n{\n    using System.Diagnostics;\n    using System.Globalization;\n    using System.Runtime.Serialization;\n    using System.Text.RegularExpressions;\n\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class Extractor : ViewModelBase, IExtractor\n    {\n        /// <summary>\n        /// Is the extractor enabled?  If so, it will remove anything matching from the output.\n        /// </summary>\n        private bool enabled;\n\n        private string name;\n\n        private string pattern;\n\n        private LogEntryFields field;\n\n        private MatchMode mode = MatchMode.Exact;\n\n        public Extractor()\n        {\n            PropertyChanged += (sender, e) =>\n            {\n                if (e.PropertyName == \"Field\" || e.PropertyName == \"Mode\" || e.PropertyName == \"Pattern\")\n                {\n                    OnPropertyChanged(nameof(Description));\n                }\n            };\n        }\n\n        public Extractor(string name, LogEntryFields field, string pattern)\n        {\n            Name = name;\n            Pattern = pattern;\n            Field = field;\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the extractor is enabled.\n        /// </summary>\n        public bool Enabled\n        {\n            get\n            {\n                return enabled;\n            }\n\n            set\n            {\n                if (value != enabled)\n                {\n                    enabled = value;\n                    OnPropertyChanged(nameof(Enabled));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (pattern != value)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                if (field != value)\n                {\n                    field = value;\n                    OnPropertyChanged(nameof(Field));\n                }\n            }\n        }\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                if (mode != value)\n                {\n                    mode = value;\n                    OnPropertyChanged(nameof(Mode));\n                }\n            }\n        }\n\n        public string Description\n        {\n            get\n            {\n                string modeDescription = \"Exact\";\n                switch (Mode)\n                {\n                    case MatchMode.RegularExpression:\n                        modeDescription = \"RegEx\";\n                        break;\n                    case MatchMode.CaseSensitive:\n                        modeDescription = \"Substring\";\n                        break;\n                }\n\n                return $\"{modeDescription} match of {Pattern} in the {Field} field\";\n            }\n        }\n\n        public bool IsMatch(ILogEntry logEntry)\n        {\n            Debug.Assert(logEntry != null, \"LogEntry can not be null.\");\n\n            if (string.IsNullOrWhiteSpace(Pattern))\n            {\n                return false;\n            }\n\n            string target;\n\n            switch (Field)\n            {\n                case LogEntryFields.None:\n                    target = string.Empty;\n                    break;\n                case LogEntryFields.Type:\n                    target = logEntry.Type;\n                    break;\n                case LogEntryFields.System:\n                    target = logEntry.System;\n                    break;\n                case LogEntryFields.Classification:\n                    target = string.Empty;\n                    break;\n                case LogEntryFields.Thread:\n                    target = logEntry.Thread;\n                    break;\n                case LogEntryFields.Source:\n                    target = logEntry.Source;\n                    break;\n                case LogEntryFields.Description:\n                    target = logEntry.Description;\n                    break;\n                case LogEntryFields.Host:\n                    target = string.Empty;\n                    break;\n                default:\n                    target = string.Empty;\n                    break;\n            }\n\n            switch (Mode)\n            {\n                case MatchMode.Exact:\n                    return !target.Equals(Pattern);\n                case MatchMode.CaseSensitive:\n                    return !target.Contains(Pattern);\n                case MatchMode.CaseInsensitive:\n                    return !target.ToUpperInvariant().Contains(Pattern.ToUpperInvariant());\n                case MatchMode.RegularExpression:\n                    var regex = new Regex(Pattern);\n                    return !regex.IsMatch(target);\n                default:\n                    return false;\n            }\n        }\n\n#if DEBUG\n        public override string ToString()\n        {\n            return Description;\n        }\n\n#endif\n    }\n}\n"
  },
  {
    "path": "Sentinel/Extractors/Gui/AddEditExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors.Gui\n{\n    using System.Windows;\n    using System.Windows.Input;\n\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    public class AddEditExtractor : ViewModelBase\n    {\n        private readonly Window window;\n\n        private string name = \"Unnamed\";\n\n        private string pattern = \"pattern\";\n\n        private LogEntryFields field;\n\n        private MatchMode mode;\n\n        public AddEditExtractor(Window window, bool editMode)\n        {\n            this.window = window;\n            if (window != null)\n            {\n                window.Title = $\"{(editMode ? \"Edit\" : \"Register\")} Extractor\";\n            }\n\n            Accept = new DelegateCommand(AcceptDialog, Validates);\n            Reject = new DelegateCommand(RejectDialog);\n        }\n\n        public ICommand Accept { get; private set; }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                field = value;\n                OnPropertyChanged(nameof(Field));\n            }\n        }\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                mode = value;\n                OnPropertyChanged(nameof(Mode));\n            }\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (value != pattern)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public ICommand Reject { get; private set; }\n\n        private void AcceptDialog(object obj)\n        {\n            window.DialogResult = true;\n            window.Close();\n        }\n\n        private void RejectDialog(object obj)\n        {\n            window.DialogResult = false;\n            window.Close();\n        }\n\n        private bool Validates(object obj)\n        {\n            return Name.Length > 0 && Pattern.Length > 0;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Gui/AddEditExtractorWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Extractors.Gui.AddEditExtractorWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:sys=\"clr-namespace:System;assembly=mscorlib\"\n        xmlns:SentinelInterfaces=\"clr-namespace:Sentinel.Interfaces\"\n        Title=\"Add / Edit Extractor\"\n        ResizeMode=\"NoResize\"\n        SizeToContent=\"WidthAndHeight\"\n        WindowStyle=\"SingleBorderWindow\"\n        Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\"\n        WindowStartupLocation=\"CenterOwner\">\n    \n    <Window.Resources>\n        \n        <ObjectDataProvider x:Key=\"logEntryField\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"SentinelInterfaces:LogEntryFields\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n        \n        <ObjectDataProvider x:Key=\"matchMode\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"SentinelInterfaces:MatchMode\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n        \n    </Window.Resources>\n\n    <DockPanel Height=\"Auto\"\n               Margin=\"5\">\n        <StackPanel Orientation=\"Horizontal\"\n                    DockPanel.Dock=\"Bottom\"\n                    Height=\"36\"\n                    HorizontalAlignment=\"Right\"\n                    Margin=\"0,10,0,0\">\n            <Button Content=\"_OK\"\n                    Command=\"{Binding Accept}\"\n                    Width=\"80\"\n                    Margin=\"0,5\"\n                    IsDefault=\"True\" />\n            <Button Content=\"_Cancel\"\n                    Command=\"{Binding Reject}\"\n                    Width=\"80\"\n                    Margin=\"5,5,0,5\"\n                    IsCancel=\"True\" />\n        </StackPanel>\n\n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n\n            <Label Content=\"Extractor name : \"\n                   Margin=\"5\" />\n            <TextBox Grid.Column=\"1\"\n                     Text=\"{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     Margin=\"5\"\n                     VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Field to match against : \"\n                   Grid.Row=\"1\"\n                   Margin=\"5\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"1\"\n                      SelectedItem=\"{Binding Field, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource logEntryField}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match method : \"\n                   Margin=\"5\"\n                   Grid.Row=\"2\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"2\"\n                      SelectedItem=\"{Binding Mode, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource matchMode}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match string :\"\n                   Margin=\"5\"\n                   Grid.Row=\"3\" />\n            <TextBox x:Name=\"matchText\"\n                     Margin=\"5\"\n                     MinWidth=\"205\"\n                     Grid.Row=\"3\"\n                     Grid.Column=\"1\"\n                     Text=\"{Binding Pattern, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     VerticalContentAlignment=\"Center\" />\n        </Grid>\n    </DockPanel>\n</Window>"
  },
  {
    "path": "Sentinel/Extractors/Gui/AddEditExtractorWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Extractors.Gui\n{\n    using System.Windows;\n\n    /// <summary>\n    /// Interaction logic for AddEditExtractorWindow.xaml.\n    /// </summary>\n    public partial class AddEditExtractorWindow : Window\n    {\n        public AddEditExtractorWindow()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Extractors/Gui/AddExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Services;\n\n    public class AddExtractor : IAddExtractorService\n    {\n        public void Add()\n        {\n            var extractorWindow = new AddEditExtractorWindow();\n            using (var data = new AddEditExtractor(extractorWindow, false))\n            {\n                extractorWindow.DataContext = data;\n                extractorWindow.Owner = Application.Current.MainWindow;\n\n                var dialogResult = extractorWindow.ShowDialog();\n                if (dialogResult == null || !(bool)dialogResult)\n                {\n                    return;\n                }\n\n                var extractor = Construct(data);\n                if (extractor == null)\n                {\n                    return;\n                }\n\n                var service = ServiceLocator.Instance.Get<IExtractingService<IExtractor>>();\n                service?.Extractors.Add(extractor);\n            }\n        }\n\n        private static Extractor Construct(AddEditExtractor data)\n        {\n            return new Extractor\n                       {\n                           Name = data.Name,\n                           Field = data.Field,\n                           Mode = data.Mode,\n                           Pattern = data.Pattern,\n                           Enabled = true,\n                       };\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Gui/EditExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors.Gui\n{\n    using System.Diagnostics;\n    using System.Windows;\n\n    using Sentinel.Extractors.Interfaces;\n\n    public class EditExtractor : IEditExtractorService\n    {\n        public void Edit(IExtractor extractor)\n        {\n            Debug.Assert(extractor != null, \"Extractor must be supplied to allow editing.\");\n\n            var window = new AddEditExtractorWindow();\n            var data = new AddEditExtractor(window, true);\n            window.DataContext = data;\n            window.Owner = Application.Current.MainWindow;\n\n            data.Name = extractor.Name;\n            data.Field = extractor.Field;\n            data.Pattern = extractor.Pattern;\n            data.Mode = extractor.Mode;\n\n            var dialogResult = window.ShowDialog();\n\n            if (dialogResult != null && (bool)dialogResult)\n            {\n                extractor.Name = data.Name;\n                extractor.Pattern = data.Pattern;\n                extractor.Mode = data.Mode;\n                extractor.Field = data.Field;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Gui/ExtractorsControl.xaml",
    "content": "﻿<UserControl\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:Controls=\"clr-namespace:Sentinel.Support.Wpf\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Sentinel.Extractors.Gui.ExtractorsControl\"\n    Name=\"extractorsControl\"\n    d:DesignWidth=\"756.67\" d:DesignHeight=\"250\">\n\n    <Grid x:Name=\"LayoutRoot\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition />\n            <ColumnDefinition Width=\"80\" />\n        </Grid.ColumnDefinitions>\n        <ListView ItemsSource=\"{Binding Extractors.Extractors}\"\n                  Grid.Column=\"0\"\n                  Grid.Row=\"0\"\n                  Grid.RowSpan=\"4\"\n                  SelectedIndex=\"{Binding Extractors.SelectedIndex}\">\n            <ListView.View>\n                <GridView AllowsColumnReorder=\"False\">\n                    <Controls:FixedWidthColumn Header=\"Enabled\"\n                                               FixedWidth=\"28\">\n                        <Controls:FixedWidthColumn.HeaderTemplate>\n                            <DataTemplate>\n                                <CheckBox IsChecked=\"True\"\n                                          IsEnabled=\"False\"\n                                          ToolTipService.ShowOnDisabled=\"True\"\n                                          ToolTip=\"Is the extractor enabled?\" />\n                            </DataTemplate>\n                        </Controls:FixedWidthColumn.HeaderTemplate>\n                        <GridViewColumn.CellTemplate>\n                            <DataTemplate>\n                                <CheckBox IsChecked=\"{Binding Enabled}\" \n                                          Margin=\"2,3,0,3\"/>\n                            </DataTemplate>\n                        </GridViewColumn.CellTemplate>\n                    </Controls:FixedWidthColumn>\n                    <GridViewColumn Header=\"Name\"\n                                    DisplayMemberBinding=\"{Binding Name}\" />\n                    <GridViewColumn Header=\"Field\"\n                                    DisplayMemberBinding=\"{Binding Field}\" />\n                    <GridViewColumn Header=\"Mode\"\n                                    DisplayMemberBinding=\"{Binding Mode}\" />\n                    <GridViewColumn Header=\"Description\"\n                                    DisplayMemberBinding=\"{Binding Description}\" />\n                </GridView>\n            </ListView.View>\n        </ListView>\n        <Button Content=\"_Add\"\n                Grid.Row=\"0\"\n                Grid.Column=\"1\"\n                Command=\"{Binding Extractors.Add}\"\n                Margin=\"5,0,5,5\" />\n        <Button Content=\"_Edit\"\n                Command=\"{Binding Extractors.Edit}\"\n                Grid.Row=\"1\"\n                Grid.Column=\"1\"\n                Margin=\"5,0,5,5\" />\n        <Button Content=\"_Remove\"\n                Command=\"{Binding Extractors.Remove}\"\n                Grid.Row=\"2\"\n                Grid.Column=\"1\"\n                Margin=\"5,0,5,5\" />\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/Extractors/Gui/ExtractorsControl.xaml.cs",
    "content": "﻿namespace Sentinel.Extractors.Gui\n{\n    using System.Windows.Controls;\n\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for ExtractorsControl.xaml.\n    /// </summary>\n    public partial class ExtractorsControl : UserControl\n    {\n        public ExtractorsControl()\n        {\n            InitializeComponent();\n            var service = ServiceLocator.Instance.Get<IExtractingService<IExtractor>>();\n            if (service != null)\n            {\n                Extractors = service;\n            }\n\n            DataContext = this;\n        }\n\n        public IExtractingService<IExtractor> Extractors { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Gui/RemoveExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Services;\n\n    public class RemoveExtractor\n        : IRemoveExtractorService\n    {\n        public void Remove(IExtractor extractor)\n        {\n            var service = ServiceLocator.Instance.Get<IExtractingService<IExtractor>>();\n\n            if (service != null)\n            {\n                var prompt = \"Are you sure you want to remove the selected extractor?\\r\\n\\r\\n\" +\n                             $\"Extractor Name = \\\"{extractor.Name}\\\"\";\n\n                var result = MessageBox.Show(\n                    prompt,\n                    \"Remove Extractor\",\n                    MessageBoxButton.YesNo,\n                    MessageBoxImage.Question,\n                    MessageBoxResult.No);\n\n                if (result == MessageBoxResult.Yes)\n                {\n                    service.Extractors.Remove(extractor);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Interfaces/IAddExtractorService.cs",
    "content": "﻿namespace Sentinel.Extractors.Interfaces\n{\n    public interface IAddExtractorService\n    {\n        void Add();\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Interfaces/IEditExtractorService.cs",
    "content": "﻿namespace Sentinel.Extractors.Interfaces\n{\n    public interface IEditExtractorService\n    {\n        void Edit(IExtractor extractor);\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Interfaces/IExtractingService.cs",
    "content": "﻿namespace Sentinel.Extractors.Interfaces\n{\n    using System.Collections.ObjectModel;\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces;\n\n    public interface IExtractingService<T>\n    {\n        [DataMember]\n        ObservableCollection<T> Extractors { get; set; }\n\n        [IgnoreDataMember]\n        ObservableCollection<T> SearchExtractors { get; set; }\n\n        bool IsFiltered(ILogEntry entry);\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Interfaces/IExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors.Interfaces\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces;\n\n    public interface IExtractor\n    {\n        [DataMember]\n        string Name { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the filter is enabled.\n        /// </summary>\n        [DataMember]\n        bool Enabled { get; set; }\n\n        [DataMember]\n        string Pattern { get; set; }\n\n        [DataMember]\n        string Description { get; }\n\n        [DataMember]\n        LogEntryFields Field { get; set; }\n\n        [DataMember]\n        MatchMode Mode { get; set; }\n\n        bool IsMatch(ILogEntry entry);\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Interfaces/IRemoveExtractorService.cs",
    "content": "﻿namespace Sentinel.Extractors.Interfaces\n{\n    public interface IRemoveExtractorService\n    {\n        void Remove(IExtractor extractor);\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/Interfaces/ISearchExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors.Interfaces\n{\n    public interface ISearchExtractor : IExtractor\n    {\n    }\n}"
  },
  {
    "path": "Sentinel/Extractors/SearchExtractor.cs",
    "content": "﻿namespace Sentinel.Extractors\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Interfaces;\n\n    [DataContract]\n    public class SearchExtractor\n        : Extractor, IDefaultInitialisation, ISearchExtractor\n    {\n        public void Initialise()\n        {\n            Name = \"SearchExtractor\";\n            Field = LogEntryFields.System;\n            Pattern = string.Empty;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/CustomMessageDecoderPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.FileMonitor.CustomMessageDecoderPage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"300\" d:DesignWidth=\"300\"\n             Loaded=\"OnLoaded\">\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition />\n        </Grid.ColumnDefinitions>\n\n        <TextBlock Margin=\"3\"\n                   Grid.Column=\"0\"\n                   Grid.ColumnSpan=\"2\"\n                   TextWrapping=\"WrapWithOverflow\">\n            <Run>Define a regular expression, using named captures, to decompose the log message into its appropriate fields.</Run>\n            <LineBreak />\n            <LineBreak />\n            <Run>An example pattern would therefore include something like:</Run>\n            <LineBreak />\n            <LineBreak />\n            <Run FontWeight=\"Bold\"\n                 FontFamily=\"Courier New\"\n                 Text=\"(?&lt;DateTime&gt;[^|]+)\" />\n            <LineBreak />\n            <LineBreak />\n            <Run>Common field names are Description, Type and DateTime and at least one of these must be matched.</Run>\n        </TextBlock>\n        <TextBlock Grid.Column=\"0\"\n                   Grid.Row=\"1\"\n                   Margin=\"3,10,3,3\"\n                   Text=\"Pattern : \" />\n        <TextBox Grid.Column=\"1\"\n                 AcceptsReturn=\"True\"\n                 Grid.Row=\"1\"\n                 Margin=\"3,10,3,3\"\n                 Text=\"{Binding CustomFormat, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}\"\n                 VerticalAlignment=\"Stretch\" />\n        <TextBlock Grid.Column=\"0\"\n                   Grid.Row=\"2\"\n                   Grid.ColumnSpan=\"2\"\n                   TextWrapping=\"WrapWithOverflow\"\n                   FontWeight=\"Bold\"\n                   Foreground=\"Red\"\n                   Text=\"{Binding Error}\"\n                   TextAlignment=\"Center\" />\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "Sentinel/FileMonitor/CustomMessageDecoderPage.xaml.cs",
    "content": "﻿namespace Sentinel.FileMonitor\n{\n    using System;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Text.RegularExpressions;\n    using System.Windows;\n    using System.Windows.Controls;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for CustomMessageDecoderPage.xaml.\n    /// </summary>\n    public partial class CustomMessageDecoderPage : IWizardPage, IDataErrorInfo\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private readonly ReadOnlyObservableCollection<IWizardPage> readonlyChildren;\n\n        private string customFormat;\n\n        private string error;\n\n        private bool isValid;\n\n        public CustomMessageDecoderPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            readonlyChildren = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            PropertyChanged += PropertyChangedHandler;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public string Title => \"Custom Message Decoder\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children => readonlyChildren;\n\n        public string Description => \"Specify how to decompose the message into its individual fields.\";\n\n        public bool IsValid\n        {\n            get => isValid;\n\n            set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent => this;\n\n        public string CustomFormat\n        {\n            get => customFormat;\n\n            set\n            {\n                if (customFormat != value)\n                {\n                    customFormat = value;\n                    OnPropertyChanged(nameof(CustomFormat));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets an error message indicating what is wrong with this object.\n        /// </summary>\n        /// <returns>\n        /// An error message indicating what is wrong with this object. The default is a null.</returns>\n        public string Error\n        {\n            get => error;\n\n            private set\n            {\n                if (error != value)\n                {\n                    error = value;\n                    OnPropertyChanged(nameof(Error));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets the error message for the property with the given name.\n        /// </summary>\n        /// <returns>\n        /// The error message for the property. The default is a null.</returns>\n        /// <param name=\"columnName\">The name of the property whose error message to get.</param>\n        public string this[string columnName]\n        {\n            get\n            {\n                if (columnName == \"CustomFormat\")\n                {\n                    string err = null;\n\n                    if (string.IsNullOrEmpty(CustomFormat))\n                    {\n                        err = \"Pattern can not be empty\";\n                    }\n                    else\n                    {\n                        // See whether the string validates as a Regex\n                        try\n                        {\n                            _ = new Regex(CustomFormat);\n\n                            // See if it contains the minimal fields\n                            if (!ContainsKeyFields(CustomFormat))\n                            {\n                                err = \"The pattern does not define any of the core fields, Description, Type or \"\n                                      + \"DateTime.  At least one of these should be defined.\";\n                            }\n                        }\n                        catch (ArgumentException)\n                        {\n                            err = \"The custom pattern does not equate to a valid regular expression\";\n                        }\n                    }\n\n                    Error = err;\n                    return err;\n                }\n\n                return null;\n            }\n        }\n\n        public object Save(object saveData)\n        {\n            // Todo: Implement page save....\n            return saveData;\n\n            //// Debug.Assert(settings != null,\n            ////             \"Settings not set, did the previous page not provide this?  \" +\n            ////             \"Was SuggestPreviousPage not called by the caller of this class?\");\n            //// settings.MessageDecoder = CustomFormat;\n            //// return settings;\n        }\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        protected virtual void OnPropertyChanged(string propertyName)\n        {\n            PropertyChangedEventHandler handler = PropertyChanged;\n            if (handler != null)\n            {\n                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private static bool ContainsKeyFields(string pattern)\n        {\n            string p = pattern.ToLower();\n\n            if (p.Contains(\"(?<description>\") || p.Contains(\"(?<type>\") || p.Contains(\"(?<datetime>\"))\n            {\n                return true;\n            }\n\n            return false;\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"CustomFormat\")\n            {\n                IsValid = this[\"CustomFormat\"] == null;\n            }\n        }\n\n        private void OnLoaded(object sender, RoutedEventArgs e)\n        {\n            // Set the custom format after the constructor to force the validation\n            // to show immediately, this will retrigger the validation.\n            OnPropertyChanged(nameof(CustomFormat));\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/FileMonitorProviderPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.FileMonitor.FileMonitorProviderPage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n             mc:Ignorable=\"d\"\n             d:DesignHeight=\"300\"\n             d:DesignWidth=\"300\">\n    <UserControl.Resources>\n        <converters:VisibilityToHiddenConverter x:Key=\"visToHidden\" />\n\n        <Style x:Key=\"textBoxInError\"\n               TargetType=\"{x:Type TextBox}\">\n            <Style.Triggers>\n                <Trigger Property=\"Validation.HasError\"\n                         Value=\"true\">\n                    <Setter Property=\"ToolTip\"\n                            Value=\"{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors).CurrentItem.ErrorContent}\" />\n                </Trigger>\n            </Style.Triggers>\n        </Style>\n    </UserControl.Resources>\n    <Grid Grid.IsSharedSizeScope=\"True\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\"/>\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\"\n                              SharedSizeGroup=\"label\" />\n            <ColumnDefinition Width=\"*\" />\n            <ColumnDefinition Width=\"Auto\"\n                              SharedSizeGroup=\"extra\" />\n        </Grid.ColumnDefinitions>\n\n        <GroupBox Margin=\"3\" Header=\"File to monitor\" Grid.ColumnSpan=\"3\">\n            <Grid>\n                <Grid.RowDefinitions>\n                    <RowDefinition />\n                    <RowDefinition />\n                </Grid.RowDefinitions>\n\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition SharedSizeGroup=\"label\" />\n                    <ColumnDefinition Width=\"*\" />\n                    <ColumnDefinition SharedSizeGroup=\"extra\" />\n                </Grid.ColumnDefinitions>\n\n                <TextBlock Grid.Column=\"0\"\n                           Grid.Row=\"0\"\n                           Margin=\"3\"\n                           Text=\"File Name : \"\n                           VerticalAlignment=\"Center\" />\n                <TextBox Grid.Column=\"1\"\n                         Grid.Row=\"0\"\n                         Margin=\"3\"\n                         Style=\"{StaticResource textBoxInError}\"\n                         Text=\"{Binding FileName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}\"\n                         VerticalAlignment=\"Center\" />\n                <Button Grid.Row=\"0\"\n                        Grid.Column=\"2\"\n                        Content=\"Browse...\"\n                        Command=\"{Binding Browse}\"\n                        VerticalAlignment=\"Center\"\n                        Width=\"75\"\n                        Height=\"23\"\n                        Margin=\"3\" />\n\n            </Grid>\n        </GroupBox>\n\n        <GroupBox Header=\"Options\"\n                  Margin=\"3\"\n                  Grid.Row=\"1\"\n                  Grid.Column=\"0\"\n                  Grid.ColumnSpan=\"3\">\n            <Grid >\n                <Grid.RowDefinitions>\n                    <RowDefinition />\n                    <RowDefinition />\n                </Grid.RowDefinitions>\n\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition SharedSizeGroup=\"label\" />\n                    <ColumnDefinition Width=\"*\" />\n                    <ColumnDefinition SharedSizeGroup=\"extra\" />\n                </Grid.ColumnDefinitions>\n\n                <TextBlock Grid.Column=\"0\"\n                           Text=\"Refresh Period : \"\n                           VerticalAlignment=\"Center\" />\n                <Slider Value=\"{Binding Refresh, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                        Minimum=\"{Binding MinRefresh}\"\n                        Maximum=\"{Binding MaxRefresh}\"\n                        Grid.Column=\"1\" \n                        Margin=\"3\"/>\n                <StackPanel Orientation=\"Horizontal\" Grid.Column=\"2\" HorizontalAlignment=\"Center\">\n                    <TextBlock Text=\"{Binding Refresh, StringFormat=0}\"\n                               Margin=\"10,0,10,0\"\n                               VerticalAlignment=\"Center\" />\n                    <TextBlock Text=\"ms\"\n                               VerticalAlignment=\"Center\" />\n                </StackPanel>\n                <CheckBox Margin=\"3\"\n                          Grid.Row=\"1\"\n                          Grid.Column=\"0\"\n                          Grid.ColumnSpan=\"3\"\n                          IsChecked=\"{Binding LoadExisting, Mode=TwoWay}\"\n                          Content=\"Load existing content from log file.\"\n                          ToolTip=\"If there are existing log entries in the file, should they be loaded into the logger?\" />\n            </Grid>\n        </GroupBox>\n\n        <StackPanel Grid.Row=\"2\"\n                    Grid.ColumnSpan=\"3\"\n                    HorizontalAlignment=\"Center\"\n                    Margin=\"10\"\n                    Visibility=\"{Binding WarnFileNotFound, Converter={StaticResource visToHidden}, UpdateSourceTrigger=PropertyChanged}\">\n            <TextBlock TextWrapping=\"WrapWithOverflow\"\n                       Margin=\"3\"\n                       Foreground=\"Red\"\n                       FontWeight=\"Bold\"\n                       Text=\"Warning, the file specified does not yet exist!\" />\n            <TextBlock Margin=\"3\"\n                       TextWrapping=\"WrapWithOverflow\"\n                       Foreground=\"Red\"\n                       FontWeight=\"Bold\"\n                       Text=\"You may continue if the log file will be created later.\"\n                       Loaded=\"OnLoaded\" />\n        </StackPanel>\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/FileMonitor/FileMonitorProviderPage.xaml.cs",
    "content": "﻿namespace Sentinel.FileMonitor\n{\n    using System;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.IO;\n    using System.Security;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Input;\n\n    using Microsoft.Win32;\n\n    using Sentinel.Interfaces.Providers;\n\n    using WpfExtras;\n\n    /// <summary>\n    ///   Interaction logic for FileMonitorProviderPage.xaml.\n    /// </summary>\n    public partial class FileMonitorProviderPage : IWizardPage, IDataErrorInfo\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private readonly ReadOnlyObservableCollection<IWizardPage> readonlyChildren;\n\n        private string fileName = string.Empty;\n\n        private bool loadExisting;\n\n        private double refresh;\n\n        private bool warnFileNotFound;\n\n        private bool isValid;\n\n        public FileMonitorProviderPage()\n        {\n            InitializeComponent();\n\n            DataContext = this;\n            readonlyChildren = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            PropertyChanged += PropertyChangedHandler;\n\n            Browse = new DelegateCommand(BrowseForFile);\n\n            // Need a subsequent page to define message format.\n            AddChild(new MessageFormatPage());\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public ICommand Browse { get; private set; }\n\n        public string FileName\n        {\n            get => fileName;\n\n            set\n            {\n                if (fileName != value)\n                {\n                    fileName = value;\n                    OnPropertyChanged(nameof(FileName));\n                }\n            }\n        }\n\n        public bool WarnFileNotFound\n        {\n            get => warnFileNotFound;\n\n            set\n            {\n                if (warnFileNotFound != value)\n                {\n                    Trace.WriteLine(\"Setting WarnFileNotFound to \" + value);\n                    warnFileNotFound = value;\n                    OnPropertyChanged(nameof(WarnFileNotFound));\n                }\n            }\n        }\n\n        public double Refresh\n        {\n            get => refresh;\n\n            set\n            {\n                if (Math.Abs(refresh - value) > 0.01)\n                {\n                    refresh = value;\n                    OnPropertyChanged(nameof(Refresh));\n                }\n            }\n        }\n\n        public int MaxRefresh => 5000;\n\n        public int MinRefresh => 50;\n\n        public bool LoadExisting\n        {\n            get => loadExisting;\n\n            set\n            {\n                if (loadExisting != value)\n                {\n                    loadExisting = value;\n                    OnPropertyChanged(nameof(LoadExisting));\n                }\n            }\n        }\n\n        public string Title => \"Log file monitoring provider\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children => readonlyChildren;\n\n        public Control PageContent => this;\n\n        public string Description => \"Configure Sentinel to monitor a file for new entries\";\n\n        public bool IsValid\n        {\n            get => isValid;\n\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        /// <summary>\n        ///   Gets an error message indicating what is wrong with this object.\n        /// </summary>\n        /// <returns>\n        ///   An error message indicating what is wrong with this object. The default is an empty string (\"\").\n        /// </returns>\n        public string Error => this[\"FileName\"];\n\n        /// <summary>\n        ///   Gets the error message for the property with the given name.\n        /// </summary>\n        /// <param name = \"columnName\">The name of the property whose error message to get.</param>\n        /// <returns>The error message for the property. The default is an empty string (\"\").</returns>\n        public string this[string columnName]\n        {\n            get\n            {\n                if (columnName != \"FileName\")\n                {\n                    return null;\n                }\n\n                if (string.IsNullOrWhiteSpace(FileName))\n                {\n                    return \"File name not specified\";\n                }\n\n                string reason;\n                return !CheckSuppliedFilenameIsValid(FileName, out reason) ? reason : null;\n            }\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData != null, \"Expecting a non-null instance of a class to save settings into\");\n            Debug.Assert(saveData is IProviderSettings, \"Expecting save object to be an IProviderSettings\");\n\n            var settings = saveData as IProviderSettings;\n\n            if (settings != null)\n            {\n                if (settings is IFileMonitoringProviderSettings fileSettings)\n                {\n                    fileSettings.Update(fileName, (int)Refresh, LoadExisting);\n                    return fileSettings;\n                }\n\n                return new FileMonitoringProviderSettings(\n                    settings.Info,\n                    settings.Name,\n                    fileName,\n                    (int)Refresh,\n                    LoadExisting);\n            }\n\n            return saveData;\n        }\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        protected virtual void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void BrowseForFile(object obj)\n        {\n            // Display the File Open Dialog.\n            var dlg = new OpenFileDialog\n                          {\n                              FileName = \"logFile\",\n                              DefaultExt = \".log\",\n                              Multiselect = false,\n                              Filter = \"Log files (.log)|*.log|Text documents (.txt)|*.txt|All files|*.*\",\n                          };\n\n            var result = dlg.ShowDialog();\n\n            if (result == true)\n            {\n                FileName = dlg.FileName;\n            }\n        }\n\n        private void OnLoaded(object sender, RoutedEventArgs e)\n        {\n            Refresh = 250;\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName != \"FileName\")\n            {\n                return;\n            }\n\n            try\n            {\n                var fi = new FileInfo(FileName);\n                WarnFileNotFound = !fi.Exists;\n                IsValid = this[\"FileName\"] == null;\n            }\n            catch (Exception)\n            {\n                // For exceptions, let the validation handler show the error.\n                WarnFileNotFound = false;\n                IsValid = false;\n            }\n        }\n\n        private bool CheckSuppliedFilenameIsValid(string fileNameToValidate, out string reason)\n        {\n            try\n            {\n                reason = null;\n                _ = new FileInfo(fileNameToValidate);\n                return true;\n            }\n            catch (UnauthorizedAccessException)\n            {\n                reason = \"The file name specified is in a location unauthorised\";\n            }\n            catch (NotSupportedException)\n            {\n                reason = \"The file name specified is not valid for a file.\";\n            }\n            catch (ArgumentException)\n            {\n                reason = \"The file name specified is not valid for a file.\";\n            }\n            catch (PathTooLongException)\n            {\n                reason = \"The file name specified is too long to be a valid file.\";\n            }\n            catch (SecurityException)\n            {\n                reason = \"You do not have permission to work with that file/location.\";\n            }\n\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/FileMonitoringProvider.cs",
    "content": "﻿namespace Sentinel.FileMonitor\n{\n    using System;\n    using System.Collections.Generic;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Globalization;\n    using System.IO;\n    using System.Linq;\n    using System.Text;\n    using System.Text.RegularExpressions;\n    using System.Threading;\n    using System.Windows;\n\n    using log4net;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Interfaces.Providers;\n\n    public class FileMonitoringProvider : ILogProvider, IDisposable\n    {\n        private const string LoggerIdentifier = \"Logger\";\n\n        private static readonly ILog Log = LogManager.GetLogger(nameof(FileMonitoringProvider));\n\n        private readonly bool loadExistingContent;\n\n        private readonly Regex patternMatching;\n\n        private readonly Queue<ILogEntry> pendingQueue = new Queue<ILogEntry>();\n\n        private readonly int refreshInterval = 250;\n\n        private readonly List<string> usedGroupNames = new List<string>();\n\n        private long bytesRead;\n\n        [System.Diagnostics.CodeAnalysis.SuppressMessage(\n            \"Microsoft.Reliability\",\n            \"CA2000: DisposeObjectsBeforeLosingScope\",\n            Justification = \"Both Worker and PurgeWorker are disposed in the IDispose implementation (or finalizer)\")]\n        public FileMonitoringProvider(IProviderSettings settings)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            var fileSettings = settings as IFileMonitoringProviderSettings;\n\n            var message = \"The FileMonitoringProvider class expects configuration information \"\n                          + \"to be of IFileMonitoringProviderSettings type\";\n            Debug.Assert(fileSettings != null, message);\n\n            ProviderSettings = fileSettings;\n            FileName = fileSettings.FileName;\n            Information = settings.Info;\n            refreshInterval = fileSettings.RefreshPeriod;\n            loadExistingContent = fileSettings.LoadExistingContent;\n            patternMatching = new Regex(fileSettings.MessageDecoder, RegexOptions.Singleline | RegexOptions.Compiled);\n\n            PredetermineGroupNames(fileSettings.MessageDecoder);\n\n            // Chain up callbacks to the workers.\n            Worker.DoWork += DoWork;\n            Worker.WorkerSupportsCancellation = true;\n            Worker.RunWorkerCompleted += DoWorkComplete;\n            PurgeWorker.DoWork += PurgeWorkerDoWork;\n            PurgeWorker.WorkerSupportsCancellation = true;\n        }\n\n        ~FileMonitoringProvider()\n        {\n            Dispose(false);\n        }\n\n        public static IProviderRegistrationRecord ProviderRegistrationInformation { get; } =\n            new ProviderRegistrationInformation(new ProviderInfo());\n\n        public string FileName { get; }\n\n        public IProviderInfo Information { get; }\n\n        public IProviderSettings ProviderSettings { get; }\n\n        public ILogger Logger { get; set; }\n\n        public string Name { get; set; }\n\n        public bool IsActive => Worker.IsBusy;\n\n        private BackgroundWorker Worker { get; set; } = new BackgroundWorker();\n\n        private BackgroundWorker PurgeWorker { get; set; } = new BackgroundWorker { WorkerReportsProgress = true };\n\n        public void Dispose()\n        {\n            Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        public void Start()\n        {\n            Debug.Assert(!string.IsNullOrEmpty(FileName), \"Filename not specified\");\n            Debug.Assert(Logger != null, \"No logger has been registered, this is required before starting a provider\");\n\n            lock (pendingQueue)\n            {\n                Log.DebugFormat(CultureInfo.InvariantCulture, \"Starting of file-monitor upon {0}\", FileName);\n            }\n\n            Worker.RunWorkerAsync();\n            PurgeWorker.RunWorkerAsync();\n        }\n\n        public void Close()\n        {\n            Worker.CancelAsync();\n            PurgeWorker.CancelAsync();\n        }\n\n        public void Pause()\n        {\n            if (Worker != null)\n            {\n                // TODO: need a better pause mechanism...\n                Close();\n            }\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            Worker?.Dispose();\n            Worker = null;\n            PurgeWorker?.Dispose();\n            PurgeWorker = null;\n        }\n\n        private void PurgeWorkerDoWork(object sender, DoWorkEventArgs e)\n        {\n            while (!e.Cancel)\n            {\n                // Go to sleep.\n                Thread.Sleep(refreshInterval);\n                lock (pendingQueue)\n                {\n                    if (pendingQueue.Any())\n                    {\n                        Log.DebugFormat(CultureInfo.InvariantCulture, \"Adding a batch of {0} entries to the logger\", pendingQueue.Count());\n                        Logger.AddBatch(pendingQueue);\n                        Trace.WriteLine(\"Done adding the batch\");\n                    }\n                }\n            }\n        }\n\n        private void PredetermineGroupNames(string messageDecoder)\n        {\n            var decoder = messageDecoder.ToUpperInvariant();\n            if (decoder.Contains(\"(?<DESCRIPTION>\"))\n            {\n                usedGroupNames.Add(\"Description\");\n            }\n\n            if (decoder.Contains(\"(?<DATETIME>\"))\n            {\n                usedGroupNames.Add(\"DateTime\");\n            }\n\n            if (decoder.Contains(\"(?<TYPE>\"))\n            {\n                usedGroupNames.Add(\"Type\");\n            }\n\n            if (decoder.Contains(\"(?<LOGGER>\"))\n            {\n                usedGroupNames.Add(LoggerIdentifier);\n            }\n        }\n\n        private void DoWork(object sender, DoWorkEventArgs e)\n        {\n            // Read existing content.\n            var fi = new FileInfo(FileName);\n\n            // Keep hold of incomplete lines, if any.\n            var incomplete = string.Empty;\n            var sb = new StringBuilder();\n\n            if (!loadExistingContent)\n            {\n                bytesRead = fi.Length;\n            }\n\n            while (!e.Cancel)\n            {\n                fi.Refresh();\n\n                if (fi.Exists)\n                {\n                    fi.Refresh();\n\n                    var length = fi.Length;\n                    if (length > bytesRead)\n                    {\n                        try\n                        {\n                            using (var fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Write))\n                            {\n                                var position = fs.Seek(bytesRead, SeekOrigin.Begin);\n                                Debug.Assert(position == bytesRead, \"Seek did not go to where we asked.\");\n\n                                // Calculate length of file.\n                                var bytesToRead = length - position;\n                                Debug.Assert(bytesToRead < int.MaxValue, \"Too much data to read using this method!\");\n\n                                var buffer = new byte[bytesToRead];\n\n                                var bytesSuccessfullyRead = fs.Read(buffer, 0, (int)bytesToRead);\n                                Debug.Assert(bytesSuccessfullyRead == bytesToRead, \"Did not get as much as expected!\");\n\n                                // Put results into a buffer (prepend any unprocessed data retained from last read).\n                                sb.Length = 0;\n                                sb.Append(incomplete);\n                                sb.Append(Encoding.ASCII.GetString(buffer, 0, bytesSuccessfullyRead));\n\n                                using (var sr = new StringReader(sb.ToString()))\n                                {\n                                    while (sr.Peek() != -1)\n                                    {\n                                        var line = sr.ReadLine();\n\n                                        // Trace.WriteLine(\"Read: \" + line);\n                                        DecodeAndQueueMessage(line);\n                                    }\n                                }\n\n                                // Can we determine whether any tailing data was unprocessed?\n                                bytesRead = position + bytesSuccessfullyRead;\n                            }\n                        }\n                        catch (Exception ex)\n                        {\n                            MessageBox.Show(\n                                $\"Error in FileMonitorProvider: {ex}\",\n                                \"Error in FileMonitorProvider\",\n                                MessageBoxButton.OK,\n                                MessageBoxImage.Error);\n                        }\n                    }\n                }\n\n                Thread.Sleep(refreshInterval);\n            }\n        }\n\n        private void DecodeAndQueueMessage(string message)\n        {\n            Debug.Assert(patternMatching != null, \"Regular expression has not be set\");\n            var m = patternMatching.Match(message);\n\n            if (!m.Success)\n            {\n                Trace.WriteLine(\"Message decoding did not work!\");\n                return;\n            }\n\n            lock (pendingQueue)\n            {\n                var entry = new LogEntry();\n\n                if (usedGroupNames.Contains(\"Description\"))\n                {\n                    entry.Description = m.Groups[\"Description\"].Value;\n                }\n\n                if (usedGroupNames.Contains(\"DateTime\"))\n                {\n                    DateTime dt;\n                    if (!DateTime.TryParse(m.Groups[\"DateTime\"].Value, out dt))\n                    {\n                        Log.DebugFormat(\n                            CultureInfo.InvariantCulture,\n                            \"Failed to parse date {0}\",\n                            m.Groups[\"DateTime\"].Value);\n                    }\n\n                    entry.DateTime = dt;\n                }\n\n                if (usedGroupNames.Contains(\"Type\"))\n                {\n                    entry.Type = m.Groups[\"Type\"].Value;\n                }\n\n                if (usedGroupNames.Contains(LoggerIdentifier))\n                {\n                    entry.Source = m.Groups[LoggerIdentifier].Value;\n                    entry.System = m.Groups[LoggerIdentifier].Value;\n                }\n\n                entry.MetaData = new Dictionary<string, object>\n                                     {\n                                         { \"Classification\", string.Empty }, { \"Host\", FileName },\n                                     };\n\n                if (entry.Description.ToUpper(CultureInfo.InvariantCulture).Contains(\"EXCEPTION\"))\n                {\n                    entry.MetaData.Add(\"Exception\", true);\n                }\n\n                pendingQueue.Enqueue(entry);\n            }\n        }\n\n        private void DoWorkComplete(object sender, RunWorkerCompletedEventArgs e)\n        {\n            Worker.CancelAsync();\n        }\n\n        private class ProviderInfo : IProviderInfo\n        {\n            public Guid Identifier => new Guid(\"1a2f8249-b390-4baa-ba5e-3d67804ba1ed\");\n\n            public string Name => \"File Monitoring Provider\";\n\n            public string Description => \"Monitor a text file for new log entries.\";\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/FileMonitoringProviderSettings.cs",
    "content": "﻿namespace Sentinel.FileMonitor\n{\n    using System.Globalization;\n\n    using Sentinel.Interfaces.Providers;\n\n    public class FileMonitoringProviderSettings : IFileMonitoringProviderSettings\n    {\n        public FileMonitoringProviderSettings(\n            IProviderInfo info,\n            string providerName,\n            string fileName,\n            int refreshPeriod,\n            bool loadExistingContent)\n        {\n            Info = info;\n            Name = providerName;\n            FileName = fileName;\n            RefreshPeriod = refreshPeriod;\n            LoadExistingContent = loadExistingContent;\n        }\n\n        public string FileName { get; private set; }\n\n        /// <summary>\n        /// Reference back to the provider this setting is appropriate to.\n        /// </summary>\n        public IProviderInfo Info { get; private set; }\n\n        public bool LoadExistingContent { get; private set; }\n\n        public string MessageDecoder { get; set; }\n\n        public string Name { get; private set; }\n\n        public int RefreshPeriod { get; private set; }\n\n        public string Summary\n            => string.Format(CultureInfo.InvariantCulture, \"Monitor the file {0} for new log entries\", FileName);\n\n        public void Update(string fileName, int refreshPeriod, bool loadExistingContent)\n        {\n            FileName = fileName;\n            RefreshPeriod = refreshPeriod;\n            LoadExistingContent = loadExistingContent;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/IFileMonitoringProviderSettings.cs",
    "content": "﻿namespace Sentinel.FileMonitor\n{\n    using Sentinel.Interfaces.Providers;\n\n    public interface IFileMonitoringProviderSettings : IProviderSettings\n    {\n        string FileName { get; }\n\n        int RefreshPeriod { get; }\n\n        bool LoadExistingContent { get; }\n\n        string MessageDecoder { get; set; }\n\n        void Update(string fileName, int refresh, bool loadExisting);\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/LogEntry.cs",
    "content": "namespace Sentinel.FileMonitor\n{\n    using System;\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n\n    internal class LogEntry : ILogEntry\n    {\n        /// <summary>\n        /// Classification for the log entry.  Can be free-text but will typically\n        /// contain values like \"DEBUG\" or \"ERROR\".\n        /// </summary>\n        public string Type { get; set; }\n\n        /// <summary>\n        /// Date/Time for the original log entry.\n        /// </summary>\n        public DateTime DateTime { get; set; }\n\n        /// <summary>\n        /// The main body of the log entry.\n        /// </summary>\n        public string Description { get; set; }\n\n        /// <summary>\n        /// Source of the log entry, e.g. where it came from.\n        /// </summary>\n        public string Source { get; set; }\n\n        /// <summary>\n        /// The system (e.g. machine) where this message came from.\n        /// </summary>\n        public string System { get; set; }\n\n        /// <summary>\n        /// Thread identifier for the source of the message.\n        /// </summary>\n        public string Thread { get; set; }\n\n        /// <summary>\n        /// Dictionary of any meta-data that doesn't fit into the above values.\n        /// </summary>\n        public Dictionary<string, object> MetaData { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/MessageFormatPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.FileMonitor.MessageFormatPage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             mc:Ignorable=\"d\"\n             d:DesignHeight=\"300\"\n             d:DesignWidth=\"300\">\n    <UserControl.Resources>\n        <BooleanToVisibilityConverter  x:Key=\"visConv\" />\n    </UserControl.Resources>\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition />\n            <RowDefinition />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition />\n        </Grid.ColumnDefinitions>\n\n        <TextBlock Grid.Column=\"0\"\n                   Margin=\"3\"\n                   Grid.ColumnSpan=\"2\"\n                   Text=\"Log entries read from a text file are generally in a flat and loosly structured format and may have lost important data.\"\n                   TextWrapping=\"WrapWithOverflow\"\n                   Loaded=\"OnLoaded\" />\n        <TextBlock Grid.Column=\"0\"\n                   Margin=\"3\"\n                   Grid.Row=\"1\"\n                   Grid.ColumnSpan=\"2\"\n                   Text=\"However by selecting an appropriate decoder, it is may be possible to extract the information and place it in the appropriate fields of the record.  If a non-standard pattern is used, it may be necessary to use the custom format.\"\n                   TextWrapping=\"WrapWithOverflow\" />\n        <TextBlock Grid.Column=\"0\"\n                   Grid.Row=\"2\"\n                   Margin=\"3\"\n                   Text=\"Decoder Format : \"\n                   TextWrapping=\"WrapWithOverflow\"\n                   VerticalAlignment=\"Center\" />\n        <ComboBox ItemsSource=\"{Binding DecodingStyles}\"\n                  Name=\"stylesCombo\"\n                  VerticalAlignment=\"Center\"\n                  Margin=\"3\"\n                  Grid.Row=\"2\"\n                  Grid.Column=\"2\"\n                  SelectedIndex=\"{Binding SelectedDecoderIndex}\" />\n        <TextBlock Grid.Row=\"3\"\n                   Grid.ColumnSpan=\"2\"\n                   HorizontalAlignment=\"Center\"\n                   Foreground=\"Red\"\n                   Visibility=\"{Binding ShowCustomWarning, Converter={StaticResource ResourceKey=visConv}}\"\n                   Text=\"Custom format is very experimental and currently requires regular expressions to be used.  It is not recommended at the moment!\"\n                   TextWrapping=\"WrapWithOverflow\" />\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/FileMonitor/MessageFormatPage.xaml.cs",
    "content": "﻿namespace Sentinel.FileMonitor\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n    using WpfExtras;\n\n    /// <summary>\n    ///   Interaction logic for MessageFormatPage.xaml.\n    /// </summary>\n    public partial class MessageFormatPage : IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private readonly ReadOnlyObservableCollection<IWizardPage> readonlyChildren;\n\n        private bool showCustomWarning = false;\n\n        private int selectedDecoderIndex;\n\n        private IWizardPage customPage = null;\n\n        public MessageFormatPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            readonlyChildren = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            PropertyChanged += PropertyChangedHandler;\n\n            DecodingStyles = new List<string>\n            {\n                \"nLog default message format decoder\",\n                \"Custom\",\n            };\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public IEnumerable<string> DecodingStyles { get; private set; }\n\n        public int SelectedDecoderIndex\n        {\n            get => selectedDecoderIndex;\n            set\n            {\n                if (selectedDecoderIndex != value)\n                {\n                    selectedDecoderIndex = value;\n                    OnPropertyChanged(nameof(SelectedDecoderIndex));\n                }\n            }\n        }\n\n        public bool ShowCustomWarning\n        {\n            get => showCustomWarning;\n\n            private set\n            {\n                if (showCustomWarning != value)\n                {\n                    showCustomWarning = value;\n                    OnPropertyChanged(nameof(ShowCustomWarning));\n                }\n            }\n        }\n\n        public string Title => \"Message Part Identification\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children => readonlyChildren;\n\n        public string Description => \"Define how the entries in the log file are categorised.\";\n\n        public bool IsValid => true;\n\n        public Control PageContent => this;\n\n        protected bool IsCustom => SelectedDecoderIndex == DecodingStyles.Count() - 1;\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData != null, \"Expecting a valid save-data instance\");\n            Debug.Assert(saveData is IFileMonitoringProviderSettings, \"Should be an IFileMonitoringProviderSettings\");\n\n            if (saveData is IFileMonitoringProviderSettings settings)\n            {\n                if (!IsCustom)\n                {\n                    settings.MessageDecoder = GetDecoder();\n                }\n            }\n\n            return saveData;\n        }\n\n        protected virtual void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            var e = new PropertyChangedEventArgs(propertyName);\n            handler?.Invoke(this, e);\n        }\n\n        private string GetDecoder()\n        {\n            switch (SelectedDecoderIndex)\n            {\n                case 0:\n                    return \"^(?<DateTime>[^|]+)\\\\|(?<Type>[^|]+)\\\\|(?<Logger>[^|]+)\\\\|(?<Description>[^$]*)$\";\n                default:\n                    throw new NotSupportedException(\"Custom message formats are not handled on this page.\");\n            }\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"SelectedDecoderIndex\")\n            {\n                if (IsCustom)\n                {\n                    if (customPage == null)\n                    {\n                        customPage = new CustomMessageDecoderPage();\n                    }\n\n                    if (!Children.Contains(customPage))\n                    {\n                        AddChild(customPage);\n                    }\n\n                    ShowCustomWarning = true;\n                }\n                else\n                {\n                    ShowCustomWarning = false;\n\n                    if (Children.Any())\n                    {\n                        children.Clear();\n                        OnPropertyChanged(nameof(Children));\n                    }\n                }\n            }\n        }\n\n        private void OnLoaded(object sender, RoutedEventArgs e)\n        {\n            // Trigger the validation on these fields (work around a WPF 3.x issue).\n            OnPropertyChanged(nameof(SelectedDecoderIndex));\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/FileMonitor/ProviderRegistrationInformation.cs",
    "content": "namespace Sentinel.FileMonitor\n{\n    using System;\n\n    using Sentinel.Interfaces.Providers;\n\n    public class ProviderRegistrationInformation : IProviderRegistrationRecord\n    {\n        public ProviderRegistrationInformation(IProviderInfo providerInfo)\n        {\n            Info = providerInfo;\n        }\n\n        public Guid Identifier\n        {\n            get\n            {\n                return Info.Identifier;\n            }\n        }\n\n        public IProviderInfo Info { get; private set; }\n\n        public Type Settings\n        {\n            get\n            {\n                return typeof(FileMonitorProviderPage);\n            }\n        }\n\n        public Type Implementer\n        {\n            get\n            {\n                return typeof(FileMonitoringProvider);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Filter.cs",
    "content": "namespace Sentinel.Filters\n{\n    using System;\n    using System.Runtime.Serialization;\n    using System.Text.RegularExpressions;\n\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class Filter : ViewModelBase, IFilter\n    {\n        /// <summary>\n        /// Is the filter enabled?  If so, it will remove anything matching from the output.\n        /// </summary>\n        private bool enabled;\n\n        private string name;\n\n        private string pattern;\n\n        private LogEntryFields field;\n\n        private MatchMode mode = MatchMode.Exact;\n\n        private Regex regex;\n\n        public Filter()\n        {\n            PropertyChanged += (sender, e) =>\n            {\n                if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) || e.PropertyName == nameof(Pattern))\n                {\n                    if (Mode == MatchMode.RegularExpression && Pattern != null)\n                    {\n                        regex = new Regex(Pattern);\n                    }\n\n                    OnPropertyChanged(nameof(Description));\n                }\n            };\n        }\n\n        public Filter(string name, LogEntryFields field, string pattern)\n        {\n            Name = name;\n            Pattern = pattern;\n            Field = field;\n            regex = new Regex(pattern);\n\n            PropertyChanged += (sender, e) =>\n            {\n                if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) || e.PropertyName == nameof(Pattern))\n                {\n                    if (Mode == MatchMode.RegularExpression && Pattern != null)\n                    {\n                        regex = new Regex(Pattern);\n                    }\n\n                    OnPropertyChanged(nameof(Description));\n                }\n            };\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the filter is enabled.\n        /// </summary>\n        public bool Enabled\n        {\n            get\n            {\n                return enabled;\n            }\n\n            set\n            {\n                if (value != enabled)\n                {\n                    enabled = value;\n                    OnPropertyChanged(nameof(Enabled));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (pattern != value)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                if (field != value)\n                {\n                    field = value;\n                    OnPropertyChanged(nameof(Field));\n                }\n            }\n        }\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                if (mode != value)\n                {\n                    mode = value;\n                    OnPropertyChanged(nameof(Mode));\n                }\n            }\n        }\n\n        public string Description\n        {\n            get\n            {\n                var modeDescription = \"Exact\";\n                switch (Mode)\n                {\n                    case MatchMode.RegularExpression:\n                        modeDescription = \"RegEx\";\n                        break;\n                    case MatchMode.CaseSensitive:\n                        modeDescription = \"Case sensitive\";\n                        break;\n                    case MatchMode.CaseInsensitive:\n                        modeDescription = \"Case insensitive\";\n                        break;\n                }\n\n                return $\"{modeDescription} match of {Pattern} in the {Field} field\";\n            }\n        }\n\n        public bool IsMatch(ILogEntry logEntry)\n        {\n            logEntry = logEntry ?? throw new ArgumentNullException(nameof(logEntry));\n\n            if (string.IsNullOrWhiteSpace(Pattern))\n            {\n                return false;\n            }\n\n            string target;\n\n            switch (Field)\n            {\n                case LogEntryFields.None:\n                    target = string.Empty;\n                    break;\n                case LogEntryFields.Type:\n                    target = logEntry.Type;\n                    break;\n                case LogEntryFields.System:\n                    target = logEntry.System;\n                    break;\n                case LogEntryFields.Classification:\n                    target = string.Empty;\n                    break;\n                case LogEntryFields.Thread:\n                    target = logEntry.Thread;\n                    break;\n                case LogEntryFields.Source:\n                    target = logEntry.Source;\n                    break;\n                case LogEntryFields.Description:\n                    target = logEntry.Description;\n                    break;\n                case LogEntryFields.Host:\n                    target = string.Empty;\n                    break;\n                default:\n                    target = string.Empty;\n                    break;\n            }\n\n            switch (Mode)\n            {\n                case MatchMode.Exact:\n                    return target.Equals(Pattern);\n                case MatchMode.CaseSensitive:\n                    return target.Contains(Pattern);\n                case MatchMode.CaseInsensitive:\n                    return target.ToLower().Contains(Pattern.ToLower());\n                case MatchMode.RegularExpression:\n                    return regex != null && regex.IsMatch(target);\n                default:\n                    return false;\n            }\n        }\n\n#if DEBUG\n        public override string ToString()\n        {\n            return Description;\n        }\n#endif\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/FilteringService.cs",
    "content": "﻿namespace Sentinel.Filters\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n\n    using Sentinel.Filters.Gui;\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Services;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class FilteringService<T> : ViewModelBase, IFilteringService<T>, IDefaultInitialisation\n        where T : class, IFilter\n    {\n        private readonly CollectionChangeHelper<T> collectionHelper = new CollectionChangeHelper<T>();\n\n        private readonly IAddFilterService addFilterService = new AddFilter();\n\n        private readonly IEditFilterService editFilterService = new EditFilter();\n\n        private readonly IRemoveFilterService removeFilterService = new RemoveFilter();\n\n        private int selectedIndex = -1;\n\n        public FilteringService()\n        {\n            Add = new DelegateCommand(AddFilter);\n            Edit = new DelegateCommand(EditFilter, e => selectedIndex != -1);\n            Remove = new DelegateCommand(RemoveFilter, e => selectedIndex != -1);\n\n            Filters = new ObservableCollection<T>();\n            SearchFilters = new ObservableCollection<T>();\n\n            // Register self as an observer of the collection.\n            collectionHelper.OnPropertyChanged += CustomFilterPropertyChanged;\n\n            collectionHelper.ManagerName = \"FilteringService\";\n            collectionHelper.NameLookup += e => e.Name;\n\n            Filters.CollectionChanged += collectionHelper.AttachDetach;\n            SearchFilters.CollectionChanged += collectionHelper.AttachDetach;\n\n            var searchFilter = ServiceLocator.Instance.Get<ISearchFilter>();\n\n            if (searchFilter != null)\n            {\n                SearchFilters.Add(searchFilter as T);\n            }\n            else\n            {\n                Debug.Fail(\"The search filter is null.\");\n            }\n        }\n\n        public ICommand Add { get; private set; }\n\n        public ICommand Edit { get; private set; }\n\n        public ICommand Remove { get; private set; }\n\n        public int SelectedIndex\n        {\n            get\n            {\n                return selectedIndex;\n            }\n\n            set\n            {\n                if (value != selectedIndex)\n                {\n                    selectedIndex = value;\n                    OnPropertyChanged(nameof(SelectedIndex));\n                }\n            }\n        }\n\n        public ObservableCollection<T> Filters { get; set; }\n\n        public ObservableCollection<T> SearchFilters { get; set; }\n\n        public void Initialise()\n        {\n            // Add the standard debugging filters\n            Filters.Add(new StandardFilter(\"Trace\", LogEntryFields.Type, \"TRACE\") as T);\n            Filters.Add(new StandardFilter(\"Debug\", LogEntryFields.Type, \"DEBUG\") as T);\n            Filters.Add(new StandardFilter(\"Info\", LogEntryFields.Type, \"INFO\") as T);\n            Filters.Add(new StandardFilter(\"Warn\", LogEntryFields.Type, \"WARN\") as T);\n            Filters.Add(new StandardFilter(\"Error\", LogEntryFields.Type, \"ERROR\") as T);\n            Filters.Add(new StandardFilter(\"Fatal\", LogEntryFields.Type, \"FATAL\") as T);\n        }\n\n        public bool IsFiltered(ILogEntry entry)\n        {\n            return Filters.Any(filter => filter.Enabled && filter.IsMatch(entry)) ||\n                SearchFilters.Any(filter => filter.Enabled && filter.IsMatch(entry));\n        }\n\n        private void AddFilter(object obj)\n        {\n            addFilterService.Add();\n        }\n\n        private void CustomFilterPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            var filter = sender as Filter;\n            if (filter != null)\n            {\n                Trace.WriteLine(\n                    $\"FilteringService saw some activity on {filter.Name} (IsEnabled = {filter.Enabled})\");\n            }\n\n            OnPropertyChanged(string.Empty);\n        }\n\n        private void EditFilter(object obj)\n        {\n            var filter = Filters.ElementAt(SelectedIndex);\n            if (filter != null)\n            {\n                editFilterService.Edit(filter);\n            }\n        }\n\n        private void RemoveFilter(object obj)\n        {\n            var filter = Filters.ElementAt(SelectedIndex);\n            removeFilterService.Remove(filter);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Gui/AddEditFilter.cs",
    "content": "namespace Sentinel.Filters.Gui\n{\n    using System.Windows;\n    using System.Windows.Input;\n\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    public class AddEditFilter : ViewModelBase\n    {\n        private readonly Window window;\n\n        private string name = \"Unnamed\";\n\n        private string pattern = \"pattern\";\n\n        private LogEntryFields field;\n\n        private MatchMode mode;\n\n        public AddEditFilter(Window window, bool editMode)\n        {\n            this.window = window;\n            if (window != null)\n            {\n                window.Title = $\"{(editMode ? \"Edit\" : \"Register\")} Filter\";\n            }\n\n            Accept = new DelegateCommand(AcceptDialog, Validates);\n            Reject = new DelegateCommand(RejectDialog);\n        }\n\n        public ICommand Accept { get; private set; }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                field = value;\n                OnPropertyChanged(nameof(Field));\n            }\n        }\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                mode = value;\n                OnPropertyChanged(nameof(Mode));\n            }\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (value != pattern)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public ICommand Reject { get; private set; }\n\n        private void AcceptDialog(object obj)\n        {\n            window.DialogResult = true;\n            window.Close();\n        }\n\n        private void RejectDialog(object obj)\n        {\n            window.DialogResult = false;\n            window.Close();\n        }\n\n        private bool Validates(object obj)\n        {\n            return Name.Length > 0\n                   && Pattern.Length > 0;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Gui/AddEditFilterWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Filters.Gui.AddEditFilterWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:sys=\"clr-namespace:System;assembly=mscorlib\"\n        xmlns:SentinelInterfaces=\"clr-namespace:Sentinel.Interfaces\"\n        Title=\"Add / Edit Filter\"\n        ResizeMode=\"NoResize\"\n        SizeToContent=\"WidthAndHeight\"\n        WindowStyle=\"SingleBorderWindow\"\n        Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\"\n        WindowStartupLocation=\"CenterOwner\">\n\n    <Window.Resources>\n\n        <ObjectDataProvider x:Key=\"logEntryField\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"SentinelInterfaces:LogEntryFields\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n\n        <ObjectDataProvider x:Key=\"matchMode\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"SentinelInterfaces:MatchMode\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n\n    </Window.Resources>\n\n    <DockPanel Height=\"Auto\"\n               Margin=\"5\">\n        <StackPanel Orientation=\"Horizontal\"\n                    DockPanel.Dock=\"Bottom\"\n                    Height=\"36\"\n                    HorizontalAlignment=\"Right\"\n                    Margin=\"0,10,0,0\">\n            <Button Content=\"_OK\"\n                    Command=\"{Binding Accept}\"\n                    Width=\"80\"\n                    Margin=\"0,5\"\n                    IsDefault=\"True\" />\n            <Button Content=\"_Cancel\"\n                    Command=\"{Binding Reject}\"\n                    Width=\"80\"\n                    Margin=\"5,5,0,5\"\n                    IsCancel=\"True\" />\n        </StackPanel>\n        \n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n\n            <Label Content=\"Filter name : \"\n                   Margin=\"5\" />\n            <TextBox Grid.Column=\"1\"\n                     Text=\"{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     Margin=\"5\"\n                     VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Field to match against : \"\n                   Grid.Row=\"1\"\n                   Margin=\"5\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"1\"\n                      SelectedItem=\"{Binding Field, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource logEntryField}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match method : \"\n                   Margin=\"5\"\n                   Grid.Row=\"2\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"2\"\n                      SelectedItem=\"{Binding Mode, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource matchMode}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match string :\"\n                   Margin=\"5\"\n                   Grid.Row=\"3\" />\n            <TextBox x:Name=\"matchText\"\n                     Margin=\"5\"\n                     MinWidth=\"205\"\n                     Grid.Row=\"3\"\n                     Grid.Column=\"1\"\n                     Text=\"{Binding Pattern, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     VerticalContentAlignment=\"Center\" />\n        </Grid>\n    </DockPanel>\n</Window>"
  },
  {
    "path": "Sentinel/Filters/Gui/AddEditFilterWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Filters.Gui\n{\n    using System.Windows;\n\n    /// <summary>\n    /// Interaction logic for AddEditFilterWindow.xaml.\n    /// </summary>\n    public partial class AddEditFilterWindow : Window\n    {\n        public AddEditFilterWindow()\n        {\n            InitializeComponent();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Gui/AddFilter.cs",
    "content": "namespace Sentinel.Filters.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Services;\n\n    public class AddFilter : IAddFilterService\n    {\n        public void Add()\n        {\n            var filterWindow = new AddEditFilterWindow();\n            using (var data = new AddEditFilter(filterWindow, false))\n            {\n                filterWindow.DataContext = data;\n                filterWindow.Owner = Application.Current.MainWindow;\n                var dialogResult = filterWindow.ShowDialog();\n                if (dialogResult != null && (bool)dialogResult)\n                {\n                    var filter = Construct(data);\n                    if (filter != null)\n                    {\n                        var service = ServiceLocator.Instance.Get<IFilteringService<IFilter>>();\n                        service?.Filters.Add(filter);\n                    }\n                }\n            }\n        }\n\n        private static Filter Construct(AddEditFilter data)\n        {\n            return new Filter\n            {\n                Name = data.Name,\n                Field = data.Field,\n                Mode = data.Mode,\n                Pattern = data.Pattern,\n                Enabled = true,\n            };\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Gui/EditFilter.cs",
    "content": "namespace Sentinel.Filters.Gui\n{\n    using System.Diagnostics;\n    using System.Windows;\n\n    using Sentinel.Filters.Interfaces;\n\n    public class EditFilter : IEditFilterService\n    {\n        public void Edit(IFilter filter)\n        {\n            Debug.Assert(filter != null, \"Filter must be supplied to allow editing.\");\n\n            var window = new AddEditFilterWindow();\n            var data = new AddEditFilter(window, true);\n            window.DataContext = data;\n            window.Owner = Application.Current.MainWindow;\n\n            data.Name = filter.Name;\n            data.Field = filter.Field;\n            data.Pattern = filter.Pattern;\n            data.Mode = filter.Mode;\n\n            var dialogResult = window.ShowDialog();\n\n            if (dialogResult != null && (bool)dialogResult)\n            {\n                filter.Name = data.Name;\n                filter.Pattern = data.Pattern;\n                filter.Mode = data.Mode;\n                filter.Field = data.Field;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Gui/FiltersControl.xaml",
    "content": "﻿<UserControl\r\n\txmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n\txmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n\txmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\r\n\txmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\r\n\txmlns:Controls=\"clr-namespace:Sentinel.Support.Wpf\"\r\n\tmc:Ignorable=\"d\"\r\n\tx:Class=\"Sentinel.Filters.Gui.FiltersControl\"\r\n\tName=\"filtersControl\"\r\n\td:DesignWidth=\"756.67\" d:DesignHeight=\"250\">\r\n\r\n    <Grid x:Name=\"LayoutRoot\">\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"*\" />\r\n        </Grid.RowDefinitions>\r\n\r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition />\r\n            <ColumnDefinition Width=\"80\" />\r\n        </Grid.ColumnDefinitions>\r\n        <ListView ItemsSource=\"{Binding Filters.Filters}\"\r\n                  Grid.Column=\"0\"\r\n                  Grid.Row=\"0\"\r\n                  Grid.RowSpan=\"4\"\r\n                  SelectedIndex=\"{Binding Filters.SelectedIndex}\">\r\n            <ListView.View>\r\n                <GridView AllowsColumnReorder=\"False\">\r\n                    <Controls:FixedWidthColumn Header=\"Enabled\"\r\n                                               FixedWidth=\"28\">\r\n                        <Controls:FixedWidthColumn.HeaderTemplate>\r\n                            <DataTemplate>\r\n                                <CheckBox IsChecked=\"True\"\r\n                                          IsEnabled=\"False\"\r\n                                          ToolTipService.ShowOnDisabled=\"True\"\r\n                                          ToolTip=\"Is the filter enabled?\" />\r\n                            </DataTemplate>\r\n                        </Controls:FixedWidthColumn.HeaderTemplate>\r\n                        <GridViewColumn.CellTemplate>\r\n                            <DataTemplate>\r\n                                <CheckBox IsChecked=\"{Binding Enabled}\" \r\n                                          Margin=\"2,3,0,3\"/>\r\n                            </DataTemplate>\r\n                        </GridViewColumn.CellTemplate>\r\n                    </Controls:FixedWidthColumn>\r\n                    <GridViewColumn Header=\"Name\"\r\n                                    DisplayMemberBinding=\"{Binding Name}\" />\r\n                    <GridViewColumn Header=\"Field\"\r\n                                    DisplayMemberBinding=\"{Binding Field}\" />\r\n                    <GridViewColumn Header=\"Mode\"\r\n                                    DisplayMemberBinding=\"{Binding Mode}\" />\r\n                    <GridViewColumn Header=\"Description\"\r\n                                    DisplayMemberBinding=\"{Binding Description}\" />\r\n                </GridView>\r\n            </ListView.View>\r\n        </ListView>\r\n        <Button Content=\"_Add\"\r\n                Grid.Row=\"0\"\r\n                Grid.Column=\"1\"\r\n                Command=\"{Binding Filters.Add}\"\r\n                Margin=\"5,0,5,5\" />\r\n        <Button Content=\"_Edit\"\r\n                Command=\"{Binding Filters.Edit}\"\r\n                Grid.Row=\"1\"\r\n                Grid.Column=\"1\"\r\n                Margin=\"5,0,5,5\" />\r\n        <Button Content=\"_Remove\"\r\n                Command=\"{Binding Filters.Remove}\"\r\n                Grid.Row=\"2\"\r\n                Grid.Column=\"1\"\r\n                Margin=\"5,0,5,5\" />\r\n    </Grid>\r\n</UserControl>"
  },
  {
    "path": "Sentinel/Filters/Gui/FiltersControl.xaml.cs",
    "content": "﻿namespace Sentinel.Filters.Gui\n{\n    using System.Windows.Controls;\n\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for FiltersControl.xaml.\n    /// </summary>\n    public partial class FiltersControl : UserControl\n    {\n        public FiltersControl()\n        {\n            InitializeComponent();\n\n            var service = ServiceLocator.Instance.Get<IFilteringService<IFilter>>();\n            if (service != null)\n            {\n                Filters = service;\n            }\n\n            DataContext = this;\n        }\n\n        public IFilteringService<IFilter> Filters { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Gui/RemoveFilter.cs",
    "content": "namespace Sentinel.Filters.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Services;\n\n    public class RemoveFilter : IRemoveFilterService\n    {\n        public void Remove(IFilter filter)\n        {\n            var service = ServiceLocator.Instance.Get<IFilteringService<IFilter>>();\n\n            if (service != null)\n            {\n                var prompt = \"Are you sure you want to remove the selected filter?\\r\\n\\r\\n\" +\n                                $\"Filter Name = \\\"{filter.Name}\\\"\";\n\n                var result = MessageBox.Show(\n                    prompt,\n                    \"Remove Filter\",\n                    MessageBoxButton.YesNo,\n                    MessageBoxImage.Question,\n                    MessageBoxResult.No);\n\n                if (result == MessageBoxResult.Yes)\n                {\n                    service.Filters.Remove(filter);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/IAddFilterService.cs",
    "content": "namespace Sentinel.Filters.Interfaces\n{\n    public interface IAddFilterService\n    {\n        void Add();\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/IEditFilterService.cs",
    "content": "namespace Sentinel.Filters.Interfaces\n{\n    public interface IEditFilterService\n    {\n        void Edit(IFilter filter);\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/IFilter.cs",
    "content": "namespace Sentinel.Filters.Interfaces\r\n{\r\n    using System.Runtime.Serialization;\r\n\r\n    using Sentinel.Interfaces;\r\n\r\n    public interface IFilter\r\n    {\r\n        [DataMember]\r\n        string Name { get; set; }\r\n\r\n        /// <summary>\r\n        /// Gets or sets a value indicating whether the filter is enabled.\r\n        /// </summary>\r\n        [DataMember]\r\n        bool Enabled { get; set; }\r\n\r\n        [DataMember]\r\n        string Pattern { get; set; }\r\n\r\n        [DataMember]\r\n        string Description { get; }\r\n\r\n        [DataMember]\r\n        LogEntryFields Field { get; set; }\r\n\r\n        [DataMember]\r\n        MatchMode Mode { get; set; }\r\n\r\n        bool IsMatch(ILogEntry entry);\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/IFilteringService.cs",
    "content": "namespace Sentinel.Filters.Interfaces\n{\n    using System.Collections.ObjectModel;\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces;\n\n    public interface IFilteringService<T>\n    {\n        [DataMember]\n        ObservableCollection<T> Filters { get; set; }\n\n        ObservableCollection<T> SearchFilters { get; set; }\n\n        bool IsFiltered(ILogEntry entry);\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/IRemoveFilterService.cs",
    "content": "namespace Sentinel.Filters.Interfaces\n{\n    public interface IRemoveFilterService\n    {\n        void Remove(IFilter filter);\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/ISearchFilter.cs",
    "content": "﻿namespace Sentinel.Filters.Interfaces\n{\n    public interface ISearchFilter : IFilter\n    {\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/Interfaces/IStandardDebuggingFilter.cs",
    "content": "namespace Sentinel.Filters.Interfaces\r\n{\r\n    public interface IStandardDebuggingFilter : IFilter\r\n    {\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Filters/SearchFilter.cs",
    "content": "﻿namespace Sentinel.Filters\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Interfaces;\n\n    [DataContract]\n    public class SearchFilter : Filter, IDefaultInitialisation, ISearchFilter\n    {\n        public void Initialise()\n        {\n            Name = \"SearchFilter\";\n            Field = LogEntryFields.System;\n            Pattern = string.Empty;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Filters/StandardFilter.cs",
    "content": "namespace Sentinel.Filters\n{\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Interfaces;\n\n    public class StandardFilter : Filter, IStandardDebuggingFilter\n    {\n        public StandardFilter(string name, LogEntryFields field, string pattern)\n            : base(name, field, pattern)\n        {\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Gui/AddEditHighlighter.cs",
    "content": "namespace Sentinel.Highlighters.Gui\n{\n    using System.Collections.Generic;\n    using System.ComponentModel;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Input;\n    using System.Windows.Media;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    using WpfExtras;\n\n    public class AddEditHighlighter : ViewModelBase\n    {\n        private readonly Window window;\n\n        private readonly Dictionary<string, Color> colours = GetColours();\n\n        private int backgroundColourIndex = 1;\n\n        private bool coloursAreClose;\n\n        private int foregroundColourIndex;\n\n        private bool overrideBackgroundColour = false;\n\n        private bool overrideForegroundColour = false;\n\n        private string name = \"Untitled\";\n\n        private string pattern = \"pattern\";\n\n        private LogEntryFields field;\n\n        private MatchMode mode;\n\n        public AddEditHighlighter(Window window, bool editMode)\n        {\n            this.window = window;\n            if (window != null)\n            {\n                window.Title = editMode ? \"Edit Highlighter\" : \"Add Highlighter\";\n            }\n\n            PropertyChanged += CloseColourCheck;\n\n            Accept = new DelegateCommand(AcceptDialog, Validates);\n            Reject = new DelegateCommand(RejectDialog);\n        }\n\n        public ICommand Accept { get; private set; }\n\n        public Color BackgroundColour\n        {\n            get\n            {\n                string key = colours.Keys.OrderBy(e => e).ToList()[backgroundColourIndex];\n                return colours[key];\n            }\n\n            set\n            {\n                var find = colours.FirstOrDefault(r => r.Value == value);\n                find.Key.ThrowIfNull(nameof(find.Key));\n\n                var index = colours.Keys.OrderBy(n => n).ToList().IndexOf(find.Key);\n                BackgroundColourIndex = index;\n            }\n        }\n\n        public int BackgroundColourIndex\n        {\n            get\n            {\n                return backgroundColourIndex;\n            }\n\n            set\n            {\n                if (value != backgroundColourIndex)\n                {\n                    backgroundColourIndex = value;\n                    OnPropertyChanged(nameof(BackgroundColourIndex));\n                }\n            }\n        }\n\n        public IEnumerable<string> BackgroundColours => colours.Keys;\n\n        public bool ColoursClose\n        {\n            get\n            {\n                return coloursAreClose;\n            }\n\n            private set\n            {\n                if (coloursAreClose != value)\n                {\n                    coloursAreClose = value;\n                    OnPropertyChanged(nameof(ColoursClose));\n                }\n            }\n        }\n\n        public Color ForegroundColour\n        {\n            get\n            {\n                string key = colours.Keys.OrderBy(e => e).ToList()[foregroundColourIndex];\n                return colours[key];\n            }\n\n            set\n            {\n                var find = colours.FirstOrDefault(r => r.Value == value);\n                find.Key.ThrowIfNull(nameof(find.Key));\n\n                var index = colours.Keys.OrderBy(n => n).ToList().IndexOf(find.Key);\n                ForegroundColourIndex = index;\n            }\n        }\n\n        public int ForegroundColourIndex\n        {\n            get\n            {\n                return foregroundColourIndex;\n            }\n\n            set\n            {\n                if (value != foregroundColourIndex)\n                {\n                    foregroundColourIndex = value;\n                    OnPropertyChanged(nameof(ForegroundColourIndex));\n                }\n            }\n        }\n\n        public IEnumerable<string> ForegroundColours => colours.Keys;\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                field = value;\n                OnPropertyChanged(nameof(Field));\n            }\n        }\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                mode = value;\n                OnPropertyChanged(nameof(Mode));\n            }\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public bool OverrideBackgroundColour\n        {\n            get\n            {\n                return overrideBackgroundColour;\n            }\n\n            set\n            {\n                if (value != overrideBackgroundColour)\n                {\n                    overrideBackgroundColour = value;\n                    OnPropertyChanged(nameof(OverrideBackgroundColour));\n                }\n            }\n        }\n\n        public bool OverrideForegroundColour\n        {\n            get\n            {\n                return overrideForegroundColour;\n            }\n\n            set\n            {\n                if (value != overrideForegroundColour)\n                {\n                    overrideForegroundColour = value;\n                    OnPropertyChanged(nameof(OverrideForegroundColour));\n                }\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (value != pattern)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public ICommand Reject { get; private set; }\n\n        private static Dictionary<string, Color> GetColours()\n        {\n            var colours = new Dictionary<string, Color>();\n            foreach (var propertyInfo in typeof(Colors).GetProperties())\n            {\n                var colour = ColorConverter.ConvertFromString(propertyInfo.Name);\n                if (colour != null)\n                {\n                    colours.Add(propertyInfo.Name, (Color)colour);\n                }\n            }\n\n            return colours;\n        }\n\n        private void AcceptDialog(object obj)\n        {\n            window.DialogResult = true;\n            window.Close();\n        }\n\n        private void CloseColourCheck(object sender, PropertyChangedEventArgs e)\n        {\n            switch (e.PropertyName)\n            {\n                case \"OverrideForegroundColour\":\n                case \"OverrideBackgroundColour\":\n                case \"BackgroundColourIndex\":\n                case \"ForegroundColourIndex\":\n                    ColoursClose = OverrideBackgroundColour && OverrideForegroundColour && Color.AreClose(ForegroundColour, BackgroundColour);\n                    break;\n            }\n        }\n\n        private void RejectDialog(object obj)\n        {\n            window.DialogResult = false;\n            window.Close();\n        }\n\n        private bool Validates(object obj)\n        {\n            return !ColoursClose\n                   && Name.Length > 0\n                   && Pattern.Length > 0;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Gui/AddEditHighlighterWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Highlighters.Gui.AddEditHighlighterWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:sys=\"clr-namespace:System;assembly=mscorlib\"\n        xmlns:interfaces=\"clr-namespace:Sentinel.Interfaces\"\n        xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n        Title=\"Add / Edit Highlighter\"\n        ResizeMode=\"NoResize\"\n        SizeToContent=\"WidthAndHeight\"\n        WindowStyle=\"SingleBorderWindow\"\n        Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\"\n        WindowStartupLocation=\"CenterOwner\">\n\n    <Window.Resources>\n        \n        <ObjectDataProvider x:Key=\"logEntryField\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"interfaces:LogEntryFields\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n\n        <ObjectDataProvider x:Key=\"matchMode\"\n                            MethodName=\"GetValues\"\n                            ObjectType=\"{x:Type sys:Enum}\">\n            <ObjectDataProvider.MethodParameters>\n                <x:Type TypeName=\"interfaces:MatchMode\" />\n            </ObjectDataProvider.MethodParameters>\n        </ObjectDataProvider>\n        \n        <Style x:Key=\"ColorSelectorCombobox\"\n               TargetType=\"ComboBox\">\n            <Setter Property=\"ItemTemplate\">\n                <Setter.Value>\n                    <DataTemplate>\n                        <StackPanel Orientation=\"Horizontal\"\n                                    Width=\"180\"\n                                    VerticalAlignment=\"Center\">\n                            <Border BorderThickness=\"1\"\n                                    Margin=\"2\"\n                                    BorderBrush=\"Black\"\n                                    CornerRadius=\"3\"\n                                    Width=\"16\"\n                                    Height=\"16\"\n                                    Background=\"{Binding}\" />\n                            <TextBlock Text=\"{Binding}\"\n                                       Margin=\"2\" />\n                        </StackPanel>\n                    </DataTemplate>\n                </Setter.Value>\n            </Setter>\n        </Style>\n\n        <converters:BooleanToDisabledConverter x:Key=\"checkboxToDisableConverter\" />\n        <converters:VisibilityToCollapsedConverter x:Key=\"collapsedVisibilityConverter\" />\n    </Window.Resources>\n\n    <DockPanel Height=\"Auto\"\n               Margin=\"5\">\n        <StackPanel Orientation=\"Horizontal\"\n                    Height=\"36\"\n                    HorizontalAlignment=\"Right\"\n                    DockPanel.Dock=\"Bottom\"\n                    Margin=\"0,10,0,0\">\n            <Button Content=\"_OK\"\n                    Command=\"{Binding Accept}\"\n                    Width=\"80\"\n                    Margin=\"0,5\"\n                    IsDefault=\"True\" />\n            <Button Content=\"_Cancel\"\n                    Command=\"{Binding Reject}\"\n                    Width=\"80\"\n                    Margin=\"5,5,0,5\"\n                    IsCancel=\"True\" />\n        </StackPanel>\n\n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n\n            <Label Content=\"Highlighter name : \"\n                   Margin=\"5\" />\n            <TextBox Grid.Column=\"1\"\n                     Text=\"{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     Margin=\"5\"\n                     VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Field to match against : \"\n                   Grid.Row=\"1\"\n                   Margin=\"5\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"1\"\n                      SelectedItem=\"{Binding Field, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource logEntryField}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match method : \"\n                   Margin=\"5\"\n                   Grid.Row=\"2\" />\n            <ComboBox Grid.Column=\"1\"\n                      Grid.Row=\"2\"\n                      SelectedItem=\"{Binding Mode, UpdateSourceTrigger=PropertyChanged}\"\n                      ItemsSource=\"{Binding Source={StaticResource matchMode}}\"\n                      Margin=\"5\"\n                      VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Match string :\"\n                   Margin=\"5\"\n                   Grid.Row=\"3\" />\n            <TextBox x:Name=\"matchText\"\n                     Margin=\"5\"\n                     MinWidth=\"205\"\n                     Grid.Row=\"3\"\n                     Grid.Column=\"1\"\n                     Text=\"{Binding Pattern, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                     VerticalContentAlignment=\"Center\" />\n\n            <Label Content=\"Foreground colour : \"\n                   Margin=\"5\"\n                   Grid.Row=\"4\" />\n            <StackPanel Margin=\"5\"\n                        Grid.Column=\"1\"\n                        Grid.Row=\"4\">\n                <CheckBox Content=\"Use default colours\"\n                          IsChecked=\"{Binding OverrideForegroundColour, Converter={StaticResource checkboxToDisableConverter}}\"\n                          Margin=\"0,5\" />\n                <ComboBox IsEnabled=\"{Binding OverrideForegroundColour}\"\n                          Margin=\"0,5\"\n                          x:Name=\"foregroundColour\"\n                          HorizontalContentAlignment=\"Center\"\n                          ItemsSource=\"{Binding ForegroundColours}\"\n                          SelectedIndex=\"{Binding ForegroundColourIndex}\"\n                          Style=\"{StaticResource ColorSelectorCombobox}\"\n                          Visibility=\"{Binding OverrideForegroundColour, Converter={StaticResource collapsedVisibilityConverter}}\"\n                          MaxDropDownHeight=\"200\" />\n\n            </StackPanel>\n\n            <Label Content=\"Background colour : \"\n                   Margin=\"5\"\n                   Grid.Row=\"5\" />\n            <StackPanel Margin=\"5\"\n                        Grid.Column=\"1\"\n                        Grid.Row=\"5\">\n                <CheckBox Content=\"Use default colours\"\n                          IsChecked=\"{Binding OverrideBackgroundColour, Converter={StaticResource checkboxToDisableConverter}}\"\n                          Margin=\"0,5\" />\n                <ComboBox Margin=\"0,5\"\n                          x:Name=\"backgroundColour\"\n                          HorizontalContentAlignment=\"Center\"\n                          IsEnabled=\"{Binding OverrideBackgroundColour}\"\n                          ItemsSource=\"{Binding BackgroundColours}\"\n                          SelectedIndex=\"{Binding BackgroundColourIndex}\"\n                          Style=\"{StaticResource ColorSelectorCombobox}\"\n                          Visibility=\"{Binding OverrideBackgroundColour, Converter={StaticResource collapsedVisibilityConverter}}\"\n                          MaxDropDownHeight=\"200\" />\n\n            </StackPanel>\n\n            <TextBlock Foreground=\"Red\"\n                       Grid.Row=\"6\"\n                       Grid.ColumnSpan=\"3\"\n                       HorizontalAlignment=\"Center\"\n                       Margin=\"5\"\n                       Visibility=\"{Binding ColoursClose, Converter={StaticResource collapsedVisibilityConverter}}\"\n                       Text=\"Warning, the colours selected may not contrast well!\" />\n        </Grid>\n    </DockPanel>\n</Window>"
  },
  {
    "path": "Sentinel/Highlighters/Gui/AddEditHighlighterWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Highlighters.Gui\n{\n    using System.Windows;\n\n    /// <summary>\n    /// Interaction logic for AddEditHighlighterWindow.xaml.\n    /// </summary>\n    public partial class AddEditHighlighterWindow : Window\n    {\n        public AddEditHighlighterWindow()\n        {\n            InitializeComponent();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Gui/AddNewHighlighterService.cs",
    "content": "﻿namespace Sentinel.Highlighters.Gui\n{\n    using System.Windows;\n    using System.Windows.Media;\n\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Services;\n\n    public class AddNewHighlighterService : IAddHighlighterService\n    {\n        public void Add()\n        {\n            var window = new AddEditHighlighterWindow();\n            var data = new AddEditHighlighter(window, false);\n            window.DataContext = data;\n            window.Owner = Application.Current.MainWindow;\n\n            var dialogResult = window.ShowDialog();\n            if (dialogResult != null && (bool)dialogResult)\n            {\n                var highlighter = Construct(data);\n\n                if (highlighter != null)\n                {\n                    var service = ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n                    service?.Highlighters.Add(highlighter);\n                }\n            }\n        }\n\n        public Highlighter Construct(AddEditHighlighter data)\n        {\n            Color? background = null;\n            Color? foreground = null;\n\n            var highlighter = new Highlighter\n                                  {\n                                      Name = data.Name,\n                                      Field = data.Field,\n                                      Pattern = data.Pattern,\n                                      Mode = data.Mode,\n                                      Enabled = true,\n                                  };\n\n            if (data.OverrideBackgroundColour)\n            {\n                background = data.BackgroundColour;\n            }\n\n            if (data.OverrideForegroundColour)\n            {\n                foreground = data.ForegroundColour;\n            }\n\n            highlighter.Style = new HighlighterStyle { Background = background, Foreground = foreground };\n            return highlighter;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Gui/EditHighlighterService.cs",
    "content": "namespace Sentinel.Highlighters.Gui\n{\n    using System.Diagnostics;\n    using System.Windows;\n    using System.Windows.Media;\n\n    using Sentinel.Highlighters.Interfaces;\n\n    // [Export(typeof(IEditHighlighterService))]\n    public class EditHighlighterService : IEditHighlighterService\n    {\n        public void Edit(IHighlighter highlighter)\n        {\n            Debug.Assert(highlighter != null, \"Highlighter must be supplied for editing.\");\n\n            var window = new AddEditHighlighterWindow();\n            var data = new AddEditHighlighter(window, false);\n            window.DataContext = data;\n            window.Owner = Application.Current.MainWindow;\n\n            data.Name = highlighter.Name;\n            data.Pattern = highlighter.Pattern;\n            data.Mode = highlighter.Mode;\n            data.Field = highlighter.Field;\n\n            if (highlighter.Style?.Background != null)\n            {\n                data.OverrideBackgroundColour = true;\n                data.BackgroundColour = (Color)highlighter.Style.Background;\n            }\n            else\n            {\n                data.OverrideBackgroundColour = false;\n                data.BackgroundColourIndex = 1;\n            }\n\n            if (highlighter.Style?.Foreground != null)\n            {\n                data.OverrideForegroundColour = true;\n                data.ForegroundColour = (Color)highlighter.Style.Foreground;\n            }\n            else\n            {\n                data.OverrideForegroundColour = false;\n                data.ForegroundColourIndex = 0;\n            }\n\n            var dialogResult = window.ShowDialog();\n\n            if (dialogResult != null && (bool)dialogResult)\n            {\n                highlighter.Name = data.Name;\n                highlighter.Pattern = data.Pattern;\n                highlighter.Mode = data.Mode;\n                highlighter.Field = data.Field;\n\n                if (highlighter.Style == null && (data.OverrideBackgroundColour || data.OverrideForegroundColour))\n                {\n                    highlighter.Style = new HighlighterStyle();\n                }\n\n                if (highlighter.Style != null)\n                {\n                    highlighter.Style.Background = data.OverrideBackgroundColour\n                                                       ? (Color?)data.BackgroundColour\n                                                       : null;\n                    highlighter.Style.Foreground = data.OverrideForegroundColour\n                                                       ? (Color?)data.ForegroundColour\n                                                       : null;\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Gui/HighlightersControl.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Highlighters.Gui.HighlightersControl\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:wpf=\"clr-namespace:Sentinel.Support.Wpf\"\r\n             xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\">\r\n\r\n    <UserControl.Resources>\r\n        <converters:ColourConverter x:Key=\"ColorConverter\" />\r\n    </UserControl.Resources>\r\n    \r\n    <DockPanel>\r\n        <StackPanel DockPanel.Dock=\"Left\"\r\n                    VerticalAlignment=\"Center\">\r\n            <StackPanel>\r\n                <Button Content=\"Up\"\r\n                        Command=\"{Binding Highlighters.OrderEarlier}\"\r\n                        Margin=\"5\" />\r\n                <Button Content=\"Down\"\r\n                        Command=\"{Binding Highlighters.OrderLater}\"\r\n                        Margin=\"5\" />\r\n            </StackPanel>\r\n        </StackPanel>\r\n        <StackPanel DockPanel.Dock=\"Right\"\r\n                    Width=\"80\">\r\n            <Button Content=\"_Add\"\r\n                    Command=\"{Binding Highlighters.Add}\"\r\n                    Margin=\"5,0,5,5\" />\r\n            <Button Content=\"_Edit\"\r\n                    Command=\"{Binding Highlighters.Edit}\"\r\n                    Margin=\"5,0,5,5\" />\r\n            <Button Content=\"_Remove\"\r\n                    Command=\"{Binding Highlighters.Remove}\"\r\n                    Margin=\"5,0,5,5\" />\r\n        </StackPanel>\r\n        <ListView Margin=\"5,0,5,0\"\r\n                  ItemsSource=\"{Binding Highlighters.Highlighters}\"\r\n                  SelectedIndex=\"{Binding Highlighters.SelectedIndex}\"\r\n                  SelectionMode=\"Single\">\r\n            <ListView.View>\r\n                <GridView>\r\n                    <wpf:FixedWidthColumn FixedWidth=\"28\">\r\n                        <wpf:FixedWidthColumn.HeaderTemplate>\r\n                            <DataTemplate>\r\n                                <CheckBox IsChecked=\"True\"\r\n                                          IsEnabled=\"False\"\r\n                                          ToolTipService.ShowOnDisabled=\"True\"\r\n                                          ToolTip=\"Is the ConcreteHighlighter enabled?\" />\r\n                            </DataTemplate>\r\n                        </wpf:FixedWidthColumn.HeaderTemplate>\r\n                        <wpf:FixedWidthColumn.CellTemplate>\r\n                            <DataTemplate>\r\n                                <CheckBox IsChecked=\"{Binding Enabled}\"\r\n                                          Margin=\"2,3,0,3\" />\r\n                            </DataTemplate>\r\n                        </wpf:FixedWidthColumn.CellTemplate>\r\n                    </wpf:FixedWidthColumn>\r\n                    <GridViewColumn Header=\"Name\"\r\n                                    DisplayMemberBinding=\"{Binding Name}\" />\r\n                    <GridViewColumn Header=\"Type\"\r\n                                    DisplayMemberBinding=\"{Binding HighlighterType}\" />\r\n                    <GridViewColumn Header=\"Mode\"\r\n                                    DisplayMemberBinding=\"{Binding Mode}\" />\r\n                    <GridViewColumn Header=\"Pattern\"\r\n                                    DisplayMemberBinding=\"{Binding Pattern}\" />\r\n                    <wpf:FixedWidthColumn Header=\"Example\"\r\n                                               FixedWidth=\"100\">\r\n                        <wpf:FixedWidthColumn.CellTemplate>\r\n                            <DataTemplate>\r\n                                <TextBlock x:Name=\"example\"\r\n                                           Text=\"Example text....\"\r\n                                           Foreground=\"{Binding Style.Foreground, Converter={StaticResource ColorConverter}, ConverterParameter=WindowText}\"\r\n                                           Background=\"{Binding Style.Background, Converter={StaticResource ColorConverter}, ConverterParameter=Window}\"\r\n                                           VerticalAlignment=\"Stretch\"\r\n                                           HorizontalAlignment=\"Stretch\" />\r\n                            </DataTemplate>\r\n                        </wpf:FixedWidthColumn.CellTemplate>\r\n                    </wpf:FixedWidthColumn>\r\n                </GridView>\r\n            </ListView.View>\r\n        </ListView>\r\n    </DockPanel>\r\n</UserControl>\r\n\r\n\r\n\r\n"
  },
  {
    "path": "Sentinel/Highlighters/Gui/HighlightersControl.xaml.cs",
    "content": "﻿namespace Sentinel.Highlighters.Gui\n{\n    using System.Windows.Controls;\n\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Services;\n\n    /// <summary>\n    /// Interaction logic for HighlightersControl.xaml.\n    /// </summary>\n    public partial class HighlightersControl : UserControl\n    {\n        public HighlightersControl()\n        {\n            InitializeComponent();\n            Highlighters = ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n            DataContext = this;\n        }\n\n        public IHighlightingService<IHighlighter> Highlighters { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Gui/RemoveHighlighterService.cs",
    "content": "﻿namespace Sentinel.Highlighters.Gui\n{\n    using System.Windows;\n\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Services;\n\n    public class RemoveHighlighterService : IRemoveHighlighterService\n    {\n        public void Remove(IHighlighter highlighter)\n        {\n            var service = ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n            if (service == null)\n            {\n                return;\n            }\n\n            var prompt = \"Are you sure you want to remove the selected highlighter?\\r\\n\\r\\n\" +\n                         $\"Highlighter Name = \\\"{highlighter.Name}\\\"\";\n\n            var result = MessageBox.Show(\n                prompt,\n                \"Remove Highlighter\",\n                MessageBoxButton.YesNo,\n                MessageBoxImage.Question,\n                MessageBoxResult.No);\n\n            if (result == MessageBoxResult.Yes)\n            {\n                service.Highlighters.Remove(highlighter);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Highlighter.cs",
    "content": "namespace Sentinel.Highlighters\n{\n    using System.Diagnostics;\n    using System.Runtime.Serialization;\n    using System.Text.RegularExpressions;\n\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class Highlighter : ViewModelBase, IHighlighter\n    {\n        private bool enabled = true;\n\n        private LogEntryFields field;\n\n        private MatchMode mode;\n\n        private string name;\n\n        private IHighlighterStyle style;\n\n        private string pattern;\n\n        private Regex regex;\n\n        public Highlighter()\n        {\n            Pattern = string.Empty;\n            Enabled = false;\n\n            PropertyChanged += (sender, e) =>\n            {\n                if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) || e.PropertyName == nameof(Pattern))\n                {\n                    if (Mode == MatchMode.RegularExpression && Pattern != null)\n                    {\n                        regex = new Regex(Pattern);\n                    }\n\n                    OnPropertyChanged(nameof(Description));\n                }\n            };\n        }\n\n        protected Highlighter(string name, bool enabled, LogEntryFields field, MatchMode mode, string pattern, IHighlighterStyle style)\n        {\n            Name = name;\n            Enabled = enabled;\n            Field = field;\n            Mode = mode;\n            Pattern = pattern;\n            Style = style;\n            regex = new Regex(pattern);\n\n            PropertyChanged += (sender, e) =>\n            {\n                if (e.PropertyName == nameof(Field) || e.PropertyName == nameof(Mode) ||\n                    e.PropertyName == nameof(Pattern))\n                {\n                    if (Mode == MatchMode.RegularExpression && Pattern != null)\n                    {\n                        regex = new Regex(Pattern);\n                    }\n\n                    OnPropertyChanged(nameof(Description));\n                }\n            };\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public bool Enabled\n        {\n            get\n            {\n                return enabled;\n            }\n\n            set\n            {\n                if (enabled != value)\n                {\n                    enabled = value;\n                    OnPropertyChanged(nameof(Enabled));\n                }\n            }\n        }\n\n        public LogEntryFields Field\n        {\n            get\n            {\n                return field;\n            }\n\n            set\n            {\n                field = value;\n                OnPropertyChanged(nameof(Field));\n            }\n        }\n\n        public string HighlighterType => \"Basic Highlighter\";\n\n        public MatchMode Mode\n        {\n            get\n            {\n                return mode;\n            }\n\n            set\n            {\n                if (mode != value)\n                {\n                    mode = value;\n                    OnPropertyChanged(nameof(Mode));\n                }\n            }\n        }\n\n        public string Description\n        {\n            get\n            {\n                string modeDescription = \"Exact\";\n                switch (Mode)\n                {\n                    case MatchMode.RegularExpression:\n                        modeDescription = \"RegEx\";\n                        break;\n                    case MatchMode.CaseSensitive:\n                        modeDescription = \"Case sensitive\";\n                        break;\n                    case MatchMode.CaseInsensitive:\n                        modeDescription = \"Case insensitive\";\n                        break;\n                }\n\n                return $\"{modeDescription} match of {Pattern} in the {Field} field\";\n            }\n        }\n\n        public string Pattern\n        {\n            get\n            {\n                return pattern;\n            }\n\n            set\n            {\n                if (pattern != value)\n                {\n                    pattern = value;\n                    OnPropertyChanged(nameof(Pattern));\n                }\n            }\n        }\n\n        public IHighlighterStyle Style\n        {\n            get\n            {\n                return style;\n            }\n\n            set\n            {\n                if (style != value)\n                {\n                    style = value;\n                    OnPropertyChanged(nameof(Style));\n                }\n            }\n        }\n\n        public bool IsMatch(ILogEntry logEntry)\n        {\n            Debug.Assert(logEntry != null, \"logEntry can not be null.\");\n\n            if (logEntry == null)\n            {\n                return false;\n            }\n\n            if (string.IsNullOrWhiteSpace(Pattern))\n            {\n                return false;\n            }\n\n            string target;\n\n            switch (Field)\n            {\n                case LogEntryFields.Type:\n                    target = logEntry.Type;\n                    break;\n                case LogEntryFields.System:\n                    target = logEntry.System;\n                    break;\n                case LogEntryFields.Thread:\n                    target = logEntry.Thread;\n                    break;\n                case LogEntryFields.Source:\n                    target = logEntry.Source;\n                    break;\n                case LogEntryFields.Description:\n                    target = logEntry.Description;\n                    break;\n                ////case LogEntryField.Classification:\n                ////case LogEntryField.None:\n                ////case LogEntryField.Host:\n                default:\n                    target = string.Empty;\n                    break;\n            }\n\n            switch (Mode)\n            {\n                case MatchMode.Exact:\n                    return target.Equals(Pattern);\n                case MatchMode.CaseSensitive:\n                    return target.Contains(Pattern);\n                case MatchMode.CaseInsensitive:\n                    return target.ToLower().Contains(Pattern.ToLower());\n                case MatchMode.RegularExpression:\n                    return regex != null && regex.IsMatch(target);\n            }\n\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/HighlighterConverter.cs",
    "content": "namespace Sentinel.Highlighters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows.Data;\n    using log4net;\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n\n    public class HighlighterConverter : IValueConverter\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(HighlighterConverter));\n\n        public HighlighterConverter(IHighlighter highlighter)\n        {\n            Highlighter = highlighter;\n        }\n\n        private IHighlighter Highlighter { get; }\n\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var match = false;\n            if (value != null)\n            {\n                var entry = value as ILogEntry;\n                if (entry == null)\n                {\n                    Log.WarnFormat(\"Expected 'value' to be an ILogEntry but found {0}\", value);\n                }\n                else\n                {\n                    match = Highlighter.Enabled && Highlighter.IsMatch(entry);\n                }\n            }\n\n            return match ? \"Match\" : \"Not Match\";\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/HighlighterStyle.cs",
    "content": "namespace Sentinel.Highlighters\n{\n    using System.Runtime.Serialization;\n    using System.Windows.Media;\n\n    using Newtonsoft.Json;\n\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class HighlighterStyle : ViewModelBase, IHighlighterStyle\n    {\n        private Color? background;\n\n        private Color? foreground;\n\n        [DataMember]\n        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]\n        public Color? Background\n        {\n            get\n            {\n                return background;\n            }\n\n            set\n            {\n                if (value != background)\n                {\n                    background = value;\n                    OnPropertyChanged(nameof(Background));\n                }\n            }\n        }\n\n        [DataMember]\n        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]\n        public Color? Foreground\n        {\n            get\n            {\n                return foreground;\n            }\n\n            set\n            {\n                if (value != foreground)\n                {\n                    foreground = value;\n                    OnPropertyChanged(nameof(Foreground));\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/HighlightingSelector.cs",
    "content": "namespace Sentinel.Highlighters\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Data;\n    using System.Windows.Input;\n    using System.Windows.Media;\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Services;\n    using Sentinel.Support.Wpf;\n\n    /// <summary>\n    /// Style selector that provides a implements the highlighters of the QuickHighlighter\n    /// (fancy name for the highlighting of results for the search box) and other registered\n    /// highlighters.  This class gets disposed of and rebuilt from scratch when the constituent\n    /// highlighters change their status.\n    /// </summary>\n    public class HighlightingSelector : StyleSelector\n    {\n        private readonly Dictionary<IHighlighter, Style> styles = new Dictionary<IHighlighter, Style>();\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"HighlightingSelector\"/> class.\n        /// </summary>\n        /// <param name=\"messagesOnMouseDoubleClick\">Action to perform on double click.</param>\n        public HighlightingSelector(Action<object, MouseButtonEventArgs> messagesOnMouseDoubleClick)\n        {\n            MessagesOnMouseDoubleClick = messagesOnMouseDoubleClick;\n            var oldState = ServiceLocator.Instance.ReportErrors;\n            ServiceLocator.Instance.ReportErrors = false;\n            var searchHighlighter = ServiceLocator.Instance.Get<ISearchHighlighter>();\n            ServiceLocator.Instance.ReportErrors = oldState;\n\n            if (searchHighlighter != null && searchHighlighter.Highlighter.Enabled)\n            {\n                var highlighter = searchHighlighter.Highlighter;\n\n                var style = new Style(typeof(ListViewItem));\n\n                var trigger = new DataTrigger\n                                  {\n                                      Binding = new Binding\n                                                    {\n                                                        ConverterParameter = highlighter,\n                                                        Converter = new HighlighterConverter(highlighter),\n                                                        Mode = BindingMode.OneWay,\n                                                    },\n                                      Value = \"Match\",\n                                  };\n\n                if (highlighter.Style != null)\n                {\n                    if (highlighter.Style.Background != null)\n                    {\n                        trigger.Setters.Add(\n                            new Setter(\n                                Control.BackgroundProperty, new SolidColorBrush((Color)highlighter.Style.Background)));\n                    }\n\n                    if (highlighter.Style.Foreground != null)\n                    {\n                        trigger.Setters.Add(\n                            new Setter(\n                                Control.ForegroundProperty, new SolidColorBrush((Color)highlighter.Style.Foreground)));\n                    }\n                }\n\n                style.Triggers.Add(trigger);\n                SetStyleSpacing(style);\n\n                // TODO: make this optional based upon the settings (but will need to rebuild highlighters when the setting changes.\n                RegisterDoubleClickEvent(style, messagesOnMouseDoubleClick);\n\n                styles[highlighter] = style;\n            }\n\n            var highlightingService = ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n\n            if (highlightingService != null)\n            {\n                foreach (var highlighter in highlightingService.Highlighters)\n                {\n                    if (highlighter != null)\n                    {\n                        // No need to create a style if not enabled, should the status of a highlighter\n                        // change, then this collection will be rebuilt.\n                        if (highlighter.Enabled)\n                        {\n                            var style = new Style(typeof(ListViewItem));\n\n                            var trigger = new DataTrigger\n                            {\n                                Binding =\n                                    new Binding\n                                    {\n                                        ConverterParameter = highlighter,\n                                        Converter = new HighlighterConverter(highlighter),\n                                        Mode = BindingMode.OneWay,\n                                    },\n                                Value = \"Match\",\n                            };\n\n                            if (highlighter.Style != null)\n                            {\n                                if (highlighter.Style.Background != null)\n                                {\n                                    trigger.Setters.Add(\n                                        new Setter(\n                                            Control.BackgroundProperty,\n                                            new SolidColorBrush((Color)highlighter.Style.Background)));\n                                }\n\n                                if (highlighter.Style.Foreground != null)\n                                {\n                                    trigger.Setters.Add(\n                                        new Setter(\n                                            Control.ForegroundProperty,\n                                            new SolidColorBrush((Color)highlighter.Style.Foreground)));\n                                }\n                            }\n\n                            // Top align values\n                            style.Setters.Add(\n                                new Setter(\n                                    Control.VerticalContentAlignmentProperty,\n                                    VerticalAlignment.Top));\n\n                            style.Triggers.Add(trigger);\n\n                            SetStyleSpacing(style);\n\n                            // TODO: make this optional based upon the settings (but will need to rebuild highlighters when the setting changes.\n                            RegisterDoubleClickEvent(style, messagesOnMouseDoubleClick);\n                            styles[highlighter] = style;\n                        }\n                    }\n                }\n            }\n        }\n\n        private Action<object, MouseButtonEventArgs> MessagesOnMouseDoubleClick { get; }\n\n        /// <summary>\n        /// Override of the <c>SelectStyle</c> method.  Looks up a suitable style for the\n        /// specified item.\n        /// </summary>\n        /// <param name=\"item\">Item to use when deciding which style to use.</param>\n        /// <param name=\"container\">Container making the request.</param>\n        /// <returns>Style to use for displaying of item.</returns>\n        public override Style SelectStyle(object item, DependencyObject container)\n        {\n            var entry = item as ILogEntry;\n            if (entry != null)\n            {\n                foreach (var pair in styles.Where(pair => pair.Key.Enabled).Where(pair => pair.Key.IsMatch(entry)))\n                {\n                    return pair.Value;\n                }\n            }\n\n            var defaultStyle = new Style(typeof(ListViewItem));\n\n            Debug.Assert(defaultStyle != null, \"Should always get a default style\");\n            SetStyleSpacing(defaultStyle);\n            RegisterDoubleClickEvent(defaultStyle, MessagesOnMouseDoubleClick);\n            defaultStyle.Setters.Add(new Setter(Control.VerticalContentAlignmentProperty, VerticalAlignment.Top));\n\n            return defaultStyle;\n        }\n\n        /// <summary>\n        /// When the user has selected to compensate for Aero style spacing between\n        /// elements, make sure that the style includes this adjustment.\n        /// </summary>\n        /// <param name=\"style\">Style to adjust spacing, if necessary.</param>\n        private static void SetStyleSpacing(Style style)\n        {\n            var preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n\n            if (preferences != null && preferences.UseTighterRows &&\n                ThemeInfo.CurrentThemeFileName == \"Aero\")\n            {\n                style.Setters.Add(\n                    new Setter(\n                        FrameworkElement.MarginProperty,\n                        new Thickness(0, -1, 0, -1)));\n            }\n        }\n\n        private void RegisterDoubleClickEvent(Style style, Action<object, MouseButtonEventArgs> handler)\n        {\n            style.ThrowIfNull(nameof(style));\n            style.Setters.Add(new EventSetter(Control.MouseDoubleClickEvent, new MouseButtonEventHandler(handler)));\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/HighlightingService.cs",
    "content": "﻿namespace Sentinel.Highlighters\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n    using System.Windows.Media;\n\n    using Sentinel.Highlighters.Gui;\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class HighlightingService<T> : ViewModelBase, IHighlightingService<T>, IDefaultInitialisation\n        where T : class, IHighlighter\n    {\n        private readonly CollectionChangeHelper<T> collectionHelper = new CollectionChangeHelper<T>();\n\n        private int selectedIndex = -1;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"HighlightingService{T}\"/> class.\n        /// </summary>\n        public HighlightingService()\n        {\n            Add = new DelegateCommand(AddHighlighter);\n            Edit = new DelegateCommand(EditHighlighter, e => selectedIndex != -1);\n            Remove = new DelegateCommand(RemoveHighlighter, e => selectedIndex != -1);\n            OrderEarlier = new DelegateCommand(MoveItemUp, e => selectedIndex > 0);\n            OrderLater = new DelegateCommand(\n                MoveItemDown,\n                e => selectedIndex < (Highlighters.Count - 1) && selectedIndex != -1);\n\n            Highlighters = new ObservableCollection<T>();\n\n            // Register self as an observer of the collection.\n            collectionHelper.ManagerName = \"Highlighters\";\n            collectionHelper.OnPropertyChanged += CustomHighlighterPropertyChanged;\n            collectionHelper.NameLookup += e => e.Name;\n            Highlighters.CollectionChanged += collectionHelper.AttachDetach;\n        }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the functionality for the adding of a new highlighter.\n        /// </summary>\n        public ICommand Add { get; private set; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> providing the functionality for editing a highlighter.\n        /// </summary>\n        public ICommand Edit { get; private set; }\n\n        /// <summary>\n        /// Gets or sets the observable collection of highlighters.\n        /// </summary>\n        [DataMember]\n        public ObservableCollection<T> Highlighters { get; set; }\n\n        public ICommand OrderEarlier { get; private set; }\n\n        public ICommand OrderLater { get; private set; }\n\n        public ICommand Remove { get; private set; }\n\n        [DataMember]\n        public int SelectedIndex\n        {\n            get\n            {\n                return selectedIndex;\n            }\n\n            set\n            {\n                if (selectedIndex != value)\n                {\n                    selectedIndex = value;\n                    OnPropertyChanged(nameof(SelectedIndex));\n                }\n            }\n        }\n\n        public IHighlighterStyle IsHighlighted(ILogEntry logEntry)\n        {\n            return Highlighters.Where(h => h.IsMatch(logEntry)).Select(h => h.Style).FirstOrDefault();\n        }\n\n        public void Initialise()\n        {\n            Debug.Assert(!Highlighters.Any(), \"Should not have any contents at initialisation\");\n\n            Highlighters.Add(\n                new StandardHighlighter(\n                    \"Trace\",\n                    true,\n                    LogEntryFields.Type,\n                    MatchMode.Exact,\n                    \"TRACE\",\n                    new HighlighterStyle { Background = Colors.LightGray }) as T);\n            Highlighters.Add(\n                new StandardHighlighter(\n                    \"Debug\",\n                    true,\n                    LogEntryFields.Type,\n                    MatchMode.Exact,\n                    \"DEBUG\",\n                    new HighlighterStyle { Background = Colors.LightGreen }) as T);\n            Highlighters.Add(\n                new StandardHighlighter(\n                    \"Info\",\n                    true,\n                    LogEntryFields.Type,\n                    MatchMode.Exact,\n                    \"INFO\",\n                    new HighlighterStyle { Foreground = Colors.White, Background = Colors.Blue }) as T);\n            Highlighters.Add(\n                new StandardHighlighter(\n                    \"Warn\",\n                    true,\n                    LogEntryFields.Type,\n                    MatchMode.Exact,\n                    \"WARN\",\n                    new HighlighterStyle { Background = Colors.Yellow }) as T);\n            Highlighters.Add(\n                new StandardHighlighter(\n                    \"Error\",\n                    true,\n                    LogEntryFields.Type,\n                    MatchMode.Exact,\n                    \"ERROR\",\n                    new HighlighterStyle { Foreground = Colors.White, Background = Colors.Red }) as T);\n            Highlighters.Add(\n                new StandardHighlighter(\n                    \"Fatal\",\n                    true,\n                    LogEntryFields.Type,\n                    MatchMode.Exact,\n                    \"FATAL\",\n                    new HighlighterStyle { Foreground = Colors.Yellow, Background = Colors.Black }) as T);\n        }\n\n        private void AddHighlighter(object obj)\n        {\n            IAddHighlighterService addHighlighterService = new AddNewHighlighterService();\n            addHighlighterService.Add();\n        }\n\n        private void CustomHighlighterPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            var highlighter = sender as IHighlighter;\n            if (highlighter != null)\n            {\n                Trace.WriteLine(\n                    $\"HighlightingService saw some activity on {highlighter.Name} (IsEnabled = {highlighter.Enabled})\");\n            }\n\n            OnPropertyChanged(string.Empty);\n        }\n\n        private void EditHighlighter(object obj)\n        {\n            IEditHighlighterService editService = new EditHighlighterService();\n            var highlighter = Highlighters.ElementAt(SelectedIndex);\n            if (highlighter != null)\n            {\n                editService.Edit(highlighter);\n            }\n        }\n\n        private void MoveItemDown(object obj)\n        {\n            if (selectedIndex == -1)\n            {\n                return;\n            }\n\n            lock (this)\n            {\n                Debug.Assert(selectedIndex >= 0, \"SelectedIndex must be valid, not -1, for moving.\");\n                Debug.Assert(\n                    selectedIndex < (Highlighters.Count - 1),\n                    \"SelectedIndex must be a value less than the max index of the collection.\");\n                Debug.Assert(\n                    Highlighters.Count > 1,\n                    \"Can only move an item if there are more than one items in the collection.\");\n\n                var oldIndex = selectedIndex;\n                SelectedIndex = -1;\n                lock (Highlighters)\n                {\n                    Highlighters.Swap(oldIndex, oldIndex + 1);\n                }\n\n                SelectedIndex = oldIndex + 1;\n            }\n        }\n\n        private void MoveItemUp(object obj)\n        {\n            if (selectedIndex == -1)\n            {\n                return;\n            }\n\n            lock (this)\n            {\n                Debug.Assert(selectedIndex >= 0, \"SelectedIndex must be valid, e.g not -1.\");\n                Debug.Assert(\n                    Highlighters.Count > 1, \"Can not move item unless more than one item in the collection.\");\n\n                var oldIndex = selectedIndex;\n                SelectedIndex = -1;\n                lock (Highlighters)\n                {\n                    Highlighters.Swap(oldIndex, oldIndex - 1);\n                }\n\n                SelectedIndex = oldIndex - 1;\n            }\n        }\n\n        private void RemoveHighlighter(object obj)\n        {\n            IRemoveHighlighterService service = new RemoveHighlighterService();\n            var highlighter = Highlighters.ElementAt(SelectedIndex);\n\n            Debug.Assert(highlighter != null, \"Should not be able to run this if no highlighter is selected!\");\n\n            service.Remove(highlighter);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/IAddHighlighterService.cs",
    "content": "namespace Sentinel.Highlighters.Interfaces\n{\n    public interface IAddHighlighterService\n    {\n        void Add();\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/IEditHighlighterService.cs",
    "content": "namespace Sentinel.Highlighters.Interfaces\n{\n    public interface IEditHighlighterService\n    {\n        void Edit(IHighlighter highlighter);\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/IHighlighter.cs",
    "content": "namespace Sentinel.Highlighters.Interfaces\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces;\n\n    public interface IHighlighter\n    {\n        [DataMember]\n        string Name { get; set; }\n\n        [DataMember]\n        bool Enabled { get; set; }\n\n        [DataMember]\n        LogEntryFields Field { get; set; }\n\n        [DataMember]\n        MatchMode Mode { get; set; }\n\n        [DataMember]\n        string Pattern { get; set; }\n\n        [DataMember]\n        string Description { get; }\n\n        [DataMember]\n        IHighlighterStyle Style { get; set; }\n\n        bool IsMatch(ILogEntry logEntry);\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/IHighlightingService.cs",
    "content": "namespace Sentinel.Highlighters.Interfaces\n{\n    using System.Collections.ObjectModel;\n    using System.Windows.Input;\n\n    using Sentinel.Interfaces;\n\n    public interface IHighlightingService<T>\n        where T : IHighlighter\n    {\n        ICommand Add { get; }\n\n        ICommand Edit { get; }\n\n        ObservableCollection<T> Highlighters { get; set; }\n\n        ICommand OrderEarlier { get; }\n\n        ICommand OrderLater { get; }\n\n        ICommand Remove { get; }\n\n        int SelectedIndex { get; set; }\n\n        IHighlighterStyle IsHighlighted(ILogEntry entry);\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/IRemoveHighlighterService.cs",
    "content": "namespace Sentinel.Highlighters.Interfaces\n{\n    public interface IRemoveHighlighterService\n    {\n        void Remove(IHighlighter highlighter);\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/ISearchHighlighter.cs",
    "content": "namespace Sentinel.Highlighters.Interfaces\n{\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n\n    public interface ISearchHighlighter\n    {\n        IEnumerable<LogEntryFields> Fields { get; }\n\n        LogEntryFields Field { get; set; }\n\n        bool Enabled { get; set; }\n\n        MatchMode Mode { get; set; }\n\n        IHighlighter Highlighter { get; }\n\n        string Search { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/Interfaces/IStandardDebuggingHighlighter.cs",
    "content": "﻿namespace Sentinel.Highlighters.Interfaces\n{\n    public interface IStandardDebuggingHighlighter : IHighlighter\n    {\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/SearchHighlighter.cs",
    "content": "namespace Sentinel.Highlighters\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Windows.Media;\n\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n\n    [DataContract]\n    public class SearchHighlighter : IDefaultInitialisation, ISearchHighlighter\n    {\n        [DataMember]\n        public IHighlighter Highlighter { get; set; }\n\n        public IEnumerable<LogEntryFields> Fields\n        {\n            get\n            {\n                var entries = Enum.GetValues(typeof(LogEntryFields)).Cast<LogEntryFields>();\n                return entries;\n            }\n        }\n\n        [DataMember]\n        public LogEntryFields Field\n        {\n            get\n            {\n                return Highlighter.Field;\n            }\n\n            set\n            {\n                Highlighter.Field = value;\n            }\n        }\n\n        [DataMember]\n        public bool Enabled\n        {\n            get\n            {\n                return Highlighter.Enabled;\n            }\n\n            set\n            {\n                Highlighter.Enabled = value;\n            }\n        }\n\n        [DataMember]\n        public MatchMode Mode\n        {\n            get\n            {\n                return Highlighter.Mode;\n            }\n\n            set\n            {\n                Highlighter.Mode = value;\n            }\n        }\n\n        [DataMember]\n        public string Search\n        {\n            get\n            {\n                Debug.Assert(Highlighter != null, \"Must have a highlighter\");\n                return Highlighter.Pattern;\n            }\n\n            set\n            {\n                Highlighter.Pattern = value;\n            }\n        }\n\n        public void Initialise()\n        {\n            Highlighter = new Highlighter\n            {\n                Name = \"Search\",\n                Style =\n                    new HighlighterStyle\n                    {\n                        Background = Colors.Lime,\n                        Foreground = Colors.Fuchsia,\n                    },\n                Field = LogEntryFields.System,\n                Mode = MatchMode.CaseSensitive,\n            };\n\n            Search = string.Empty;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Highlighters/StandardHighlighter.cs",
    "content": "﻿namespace Sentinel.Highlighters\n{\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n\n    public class StandardHighlighter : Highlighter, IStandardDebuggingHighlighter\n    {\n        public StandardHighlighter(string name, bool enabled, LogEntryFields field, MatchMode mode, string pattern, IHighlighterStyle style)\n            : base(name, enabled, field, mode, pattern, style)\n        {\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/AddEditTypeImageViewModel.cs",
    "content": "namespace Sentinel.Images\n{\n    using System;\n    using System.Collections.Generic;\n    using System.ComponentModel;\n    using System.IO;\n    using System.Windows;\n    using System.Windows.Input;\n    using System.Windows.Media.Imaging;\n    using Microsoft.Win32;\n    using Sentinel.Images.Interfaces;\n    using WpfExtras;\n\n    public class AddEditTypeImageViewModel\n        : ViewModelBase, IDataErrorInfo\n    {\n        private readonly Dictionary<ImageError, string> imageErrorMessages = new Dictionary<ImageError, string>\n                                                                                 {\n                                                                                     {\n                                                                                         ImageError\n                                                                                         .NotSpecified,\n                                                                                         \"No image selected.\"\n                                                                                     },\n                                                                                     {\n                                                                                         ImageError\n                                                                                         .NotFound,\n                                                                                         \"Image not found.\"\n                                                                                     },\n                                                                                     {\n                                                                                         ImageError\n                                                                                         .TooLarge,\n                                                                                         \"Image must be less than 128x128. [Note: images are most often used at 64x64]\"\n                                                                                     },\n                                                                                     {\n                                                                                         ImageError\n                                                                                         .Unknown,\n                                                                                         \"Problem with reading image.\"\n                                                                                     },\n                                                                                     {\n                                                                                         ImageError\n                                                                                         .NoError,\n                                                                                         null\n                                                                                     },\n                                                                                 };\n\n        private readonly Dictionary<TypeError, string> typeErrorMessages = new Dictionary<TypeError, string>\n                                                                               {\n                                                                                   {\n                                                                                       TypeError\n                                                                                       .NotSpecified,\n                                                                                       \"Type name must be specified.\"\n                                                                                   },\n                                                                                   {\n                                                                                       TypeError\n                                                                                       .Duplicate,\n                                                                                       \"There is already an entry for this type name and size combination.\"\n                                                                                   },\n                                                                                   {\n                                                                                       TypeError\n                                                                                       .NoError,\n                                                                                       null\n                                                                                   },\n                                                                               };\n\n        private string errorMessage = \"No image selected.\";\n\n        private string fileName;\n\n        private BitmapImage image;\n\n        private ImageError imageError;\n\n        private string type = string.Empty;\n\n        private string size = \"Small\";\n\n        private TypeError typeError;\n\n        private bool isAddMode;\n\n        public AddEditTypeImageViewModel(Window window, ITypeImageService images, bool isAddMode)\n        {\n            Window = window;\n            if (window != null)\n            {\n                window.Title = $\"{(isAddMode ? \"Edit\" : \"Add\")} Image\";\n            }\n\n            Window.DataContext = this;\n\n            IsAddMode = isAddMode;\n            imageError = isAddMode ? ImageError.NotSpecified : ImageError.NoError;\n            typeError = isAddMode ? TypeError.NotSpecified : TypeError.NoError;\n\n            Accept = new DelegateCommand(e => CloseDialog(true), c => IsValid);\n            Reject = new DelegateCommand(e => CloseDialog(false));\n            Browse = new DelegateCommand(BrowseForImageFiles);\n\n            ImageService = images;\n\n            UpdateErrorMessage(false);\n\n            PropertyChanged += (sender, e) =>\n                {\n                    if (e.PropertyName == \"FileName\")\n                    {\n                        LoadImageFromFileName(FileName);\n                    }\n                };\n        }\n\n        /// <summary>\n        /// Error messages corresponding to the image field.\n        /// </summary>\n        private enum ImageError\n        {\n            /// <summary>\n            /// No image yet specified.\n            /// </summary>\n            NotSpecified,\n\n            /// <summary>\n            /// Image can not be found.\n            /// </summary>\n            NotFound,\n\n            /// <summary>\n            /// Image is too large for the purposes.\n            /// </summary>\n            TooLarge,\n\n            /// <summary>\n            /// Some unknown error.\n            /// </summary>\n            Unknown,\n\n            /// <summary>\n            /// No error condition encountered.\n            /// </summary>\n            NoError,\n        }\n\n        /// <summary>\n        /// Errors corresponding to the data validation of the Type field.\n        /// </summary>\n        private enum TypeError\n        {\n            /// <summary>\n            /// Type name has not been specified.\n            /// </summary>\n            NotSpecified,\n\n            /// <summary>\n            /// Type name duplicates another.\n            /// </summary>\n            Duplicate,\n\n            /// <summary>\n            /// No error encountered.\n            /// </summary>\n            NoError,\n        }\n\n        public Window Window { get; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> corresponding to the Accept action.\n        /// </summary>\n        public ICommand Accept { get; private set; }\n\n        /// <summary>\n        /// Gets the <c>ICommand</c> corresponding to the Browse action.\n        /// </summary>\n        public ICommand Browse { get; private set; }\n\n        /// <summary>\n        /// Gets or sets the filename for the icon to use.\n        /// </summary>\n        public string FileName\n        {\n            get\n            {\n                return fileName;\n            }\n\n            set\n            {\n                if (fileName != value)\n                {\n                    fileName = value;\n                    OnPropertyChanged(nameof(FileName));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets the image to use as the type icon.\n        /// </summary>\n        public BitmapImage Image\n        {\n            get\n            {\n                return image;\n            }\n\n            set\n            {\n                if (!Equals(image, value))\n                {\n                    image = value;\n                    OnPropertyChanged(nameof(Image));\n                }\n            }\n        }\n\n        public bool IsValid => typeError == TypeError.NoError && imageError == ImageError.NoError;\n\n        public ICommand Reject { get; private set; }\n\n        /// <summary>\n        /// Gets or sets the type of a TypeImage.\n        /// </summary>\n        public string Type\n        {\n            get\n            {\n                return type;\n            }\n\n            set\n            {\n                if (type != value)\n                {\n                    type = value;\n                    OnPropertyChanged(nameof(Type));\n                }\n            }\n        }\n\n        public string Size\n        {\n            get\n            {\n                return size;\n            }\n\n            set\n            {\n                if (size != value)\n                {\n                    size = value;\n                    OnPropertyChanged(nameof(Size));\n                }\n            }\n        }\n\n        public bool IsAddMode\n        {\n            get\n            {\n                return isAddMode;\n            }\n\n            set\n            {\n                if (isAddMode != value)\n                {\n                    isAddMode = value;\n                    OnPropertyChanged(nameof(IsAddMode));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets an error message indicating what is wrong with this object.\n        /// </summary>\n        /// <returns>\n        /// An error message indicating what is wrong with this object. The default is an empty string (\"\").\n        /// </returns>\n        public string Error\n        {\n            get\n            {\n                return errorMessage;\n            }\n\n            private set\n            {\n                if (errorMessage != value)\n                {\n                    errorMessage = value;\n                    OnPropertyChanged(nameof(Error));\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        private ITypeImageService ImageService { get; set; }\n\n        /// <summary>\n        /// Gets the error message for the property with the given name.\n        /// </summary>\n        /// <returns>\n        /// The error message for the property. The default is an empty string (\"\").\n        /// </returns>\n        /// <param name=\"fieldName\">The name of the property whose error message to get.\n        /// </param>\n        public string this[string fieldName]\n        {\n            get\n            {\n                string error = null;\n\n                if (IsAddMode && (fieldName == \"Type\" || fieldName == \"Size\"))\n                {\n                    var oldTypeError = typeError;\n\n                    var options = new ImageOptions\n                                      {\n                                          Quality = (ImageQuality)Enum.Parse(typeof(ImageQuality), Size),\n                                          AcceptLowerQuality = true,\n                                      };\n                    if (!string.IsNullOrEmpty(Type) && ImageService?.Get(Type, options) != null)\n                    {\n                        typeError = TypeError.Duplicate;\n                    }\n                    else if (fieldName == \"Type\" && string.IsNullOrEmpty(Type))\n                    {\n                        typeError = TypeError.NotSpecified;\n                    }\n                    else if (fieldName == \"Type\" || fieldName == \"Size\")\n                    {\n                        typeError = TypeError.NoError;\n                    }\n\n                    error = typeErrorMessages[typeError];\n\n                    if (oldTypeError != typeError)\n                    {\n                        UpdateErrorMessage(false);\n                    }\n                }\n\n                return error;\n            }\n        }\n\n        private void BrowseForImageFiles(object obj)\n        {\n            var openFileDialog = new OpenFileDialog\n                                     {\n                                         Title = \"Select image\",\n                                         ValidateNames = true,\n                                         CheckFileExists = true,\n                                         Multiselect = false,\n                                         Filter = \"Images files|*.png;*.bmp|All Files|*.*\",\n                                         InitialDirectory =\n                                             Environment.GetFolderPath(\n                                                 Environment.SpecialFolder.MyPictures),\n                                     };\n\n            var dialogResult = openFileDialog.ShowDialog(Window);\n\n            if (dialogResult == true)\n            {\n                // LoadImageFromFileName(openFileDialog.FileName);\n\n                // In all cases, keep the filename entered or it is confusing for the user.\n                // Plus, we need to raise the PropertyChanged event for the FileName property.\n                FileName = openFileDialog.FileName;\n            }\n        }\n\n        private void LoadImageFromFileName(string fileName)\n        {\n            var oldImageError = imageError;\n\n            try\n            {\n                // Check file exists, it should!\n                var fi = new FileInfo(fileName);\n                if (fi.Exists)\n                {\n                    var i = new BitmapImage();\n                    i.BeginInit();\n                    i.UriSource = new Uri(fileName, UriKind.RelativeOrAbsolute);\n                    i.EndInit();\n\n                    Image = i;\n\n                    // Some sanity checking.\n                    imageError = i.Width <= 128 && i.Height <= 128 ? ImageError.NoError : ImageError.TooLarge;\n                }\n                else\n                {\n                    Image = null;\n                    imageError = ImageError.NotFound;\n                }\n            }\n            catch (Exception)\n            {\n                Image = null;\n                imageError = ImageError.Unknown;\n            }\n\n            if (imageError != oldImageError)\n            {\n                UpdateErrorMessage(true);\n            }\n        }\n\n        private void CloseDialog(bool dialogResult)\n        {\n            Window.DialogResult = dialogResult;\n            Window.Close();\n        }\n\n        private void UpdateErrorMessage(bool imageChangedMostRecently)\n        {\n            var oldErrorMessage = Error;\n            var imageErrorMessage = imageErrorMessages[imageError];\n            var typeErrorMessage = typeErrorMessages[typeError];\n\n            var newErrorMessage = imageChangedMostRecently\n                                      ? (imageErrorMessage ?? typeErrorMessage)\n                                      : (typeErrorMessage ?? imageErrorMessage);\n\n            if (oldErrorMessage != newErrorMessage)\n            {\n                Error = newErrorMessage;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/AddTypeImageService.cs",
    "content": "namespace Sentinel.Images\n{\n    using System;\n    using System.Windows;\n\n    using Sentinel.Images.Controls;\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Services;\n\n    using WpfExtras;\n\n    public class AddTypeImageService : ViewModelBase, IAddTypeImage\n    {\n        private AddImageWindow addImageWindow;\n\n        public void Add()\n        {\n            if (addImageWindow != null)\n            {\n                MessageBox.Show(\n                    \"Only able to have one add image dialog open at a time!\",\n                    \"Add image\",\n                    MessageBoxButton.OK,\n                    MessageBoxImage.Error);\n            }\n            else\n            {\n                addImageWindow = new AddImageWindow { DataContext = this, Owner = Application.Current.MainWindow };\n\n                var imageService = ServiceLocator.Instance.Get<ITypeImageService>();\n                var data = new AddEditTypeImageViewModel(addImageWindow, imageService, true);\n                var dialogResult = addImageWindow.ShowDialog();\n\n                if (dialogResult == true)\n                {\n                    if (imageService != null)\n                    {\n                        var imageSize = (ImageQuality)Enum.Parse(typeof(ImageQuality), data.Size);\n                        imageService.Register(data.Type, imageSize, data.FileName);\n                    }\n                }\n\n                addImageWindow = null;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/Controls/AddImageWindow.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.Images.Controls.AddImageWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n        Title=\"Add Image\"\n        WindowStartupLocation=\"CenterOwner\"\n        ShowInTaskbar=\"False\"\n        WindowStyle=\"SingleBorderWindow\"\n        MinHeight=\"400\"\n        MinWidth=\"470\"\n        SizeToContent=\"WidthAndHeight\">\n\n    <Window.Resources>\n        <converters:VisibilityToHiddenConverter x:Key=\"visConverter\" />\n    </Window.Resources>\n    \n    <DockPanel Height=\"Auto\"\n                   Margin=\"6\">\n            <StackPanel Orientation=\"Horizontal\"\n                        Height=\"36\"\n                        HorizontalAlignment=\"Right\"\n                        DockPanel.Dock=\"Bottom\"\n                        Margin=\"0,10,0,0\">\n                <Button Content=\"_OK\"\n                        Command=\"{Binding Accept}\"\n                        Width=\"80\"\n                        Margin=\"0,5\"\n                        IsDefault=\"True\" />\n                <Button Content=\"_Cancel\"\n                        Command=\"{Binding Reject}\"\n                        Width=\"80\"\n                        Margin=\"5,5,0,5\"\n                        IsCancel=\"True\" />\n            </StackPanel>\n\n        <Grid>\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"*\"/>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"Auto\" />\n            </Grid.RowDefinitions>\n\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\" />\n                <ColumnDefinition Width=\"*\"/>\n                <ColumnDefinition Width=\"Auto\" />\n            </Grid.ColumnDefinitions>\n\n            <TextBlock Grid.Row=\"0\"\n                       Text=\"Type name : \"\n                       Margin=\"5\"\n                       VerticalAlignment=\"Center\" />\n            <TextBox Grid.Row=\"0\"\n                     Grid.Column=\"1\" \n                     Grid.ColumnSpan=\"2\"\n                     Margin=\"5\"\n                     VerticalAlignment=\"Center\"\n                     IsEnabled=\"{Binding IsAddMode}\"\n                     Text=\"{Binding Type, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}\"/>\n            \n            <TextBlock Grid.Row=\"1\"\n                       Text=\"Size : \"\n                       Margin=\"5\"\n                       VerticalAlignment=\"Center\" />\n            <ComboBox Grid.Row=\"1\"\n                      Grid.Column=\"1\"\n                      Grid.ColumnSpan=\"2\"\n                      Margin=\"5\"\n                      SelectedIndex=\"0\"\n                      SelectedValuePath=\"Content\"\n                      IsEnabled=\"{Binding IsAddMode}\"\n                      SelectedValue=\"{Binding Size, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}\">\n                <ComboBoxItem Content=\"Small\" />\n                <ComboBoxItem Content=\"Medium\" />\n                <ComboBoxItem Content=\"Large\" />\n            </ComboBox>\n            \n            <Border Grid.Row=\"2\"\n                    Grid.Column=\"0\"\n                    Grid.RowSpan=\"1\"\n                    Grid.ColumnSpan=\"3\"\n                    Margin=\"5\"\n                    Padding=\"5\"\n                    CornerRadius=\"3\"\n                    BorderThickness=\"1\"\n                    BorderBrush=\"Black\">\n                <Image x:Name=\"imagePreview\"\n                       Source=\"{Binding Image}\"\n                       MaxWidth=\"128\"\n                       Width=\"Auto\"\n                       MaxHeight=\"128\"\n                       Height=\"Auto\"\n                       Margin=\"15\"\n                       Stretch=\"None\" />\n            </Border>\n            <TextBlock Grid.Row=\"3\"\n                       Text=\"Image file : \"\n                       Margin=\"5\"\n                       VerticalAlignment=\"Center\" />\n            <TextBox Grid.Row=\"3\"\n                     Grid.Column=\"1\"\n                     Text=\"{Binding FileName, UpdateSourceTrigger=PropertyChanged}\"\n                     Margin=\"5\"\n                     VerticalAlignment=\"Center\"\n                     IsReadOnly=\"True\" />\n            <Button Content=\"_...\"\n                    Command=\"{Binding Browse}\"\n                    Grid.Row=\"3\"\n                    Grid.Column=\"2\"\n                    VerticalAlignment=\"Top\"\n                    HorizontalAlignment=\"Center\"\n                    Width=\"22\"\n                    Height=\"22\"\n                    Margin=\"2,5,0,5\"/>\n            <TextBlock Grid.Row=\"5\"\n                       Grid.ColumnSpan=\"3\"\n                       Foreground=\"Red\"\n                       Margin=\"5\"\n                       TextWrapping=\"WrapWithOverflow\"\n                       Visibility=\"{Binding IsValid, Converter={StaticResource visConverter}, ConverterParameter=true}\"\n                       VerticalAlignment=\"Center\"\n                       HorizontalAlignment=\"Center\"\n                       Text=\"{Binding Error}\" />\n        </Grid>\n    </DockPanel>\n</Window>"
  },
  {
    "path": "Sentinel/Images/Controls/AddImageWindow.xaml.cs",
    "content": "﻿namespace Sentinel.Images.Controls\n{\n    using System.Windows;\n\n    /// <summary>\n    /// Interaction logic for AddImageWindow.xaml.\n    /// </summary>\n    public partial class AddImageWindow : Window\n    {\n        public AddImageWindow()\n        {\n            InitializeComponent();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/EditTypeImageMapping.cs",
    "content": "namespace Sentinel.Images\n{\n    using System.Diagnostics;\n    using System.Windows;\n\n    using Sentinel.Images.Controls;\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Services;\n\n    public class EditTypeImageMapping : IEditTypeImage\n    {\n        public void Edit(ImageTypeRecord imageTypeRecord)\n        {\n            Debug.Assert(imageTypeRecord != null, \"ImageTypeRecord must be supplied to allow editing.\");\n\n            var service = ServiceLocator.Instance.Get<ITypeImageService>();\n\n            var window = new AddImageWindow();\n            var data = new AddEditTypeImageViewModel(window, service, false);\n            window.DataContext = data;\n            window.Owner = Application.Current.MainWindow;\n\n            data.Type = imageTypeRecord.Name;\n            data.Size = imageTypeRecord.Quality.ToString();\n            data.FileName = imageTypeRecord.Image;\n\n            bool? dialogResult = window.ShowDialog();\n\n            if (dialogResult != null && (bool)dialogResult)\n            {\n                imageTypeRecord.Image = data.FileName;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/ImageQuality.cs",
    "content": "namespace Sentinel.Images\r\n{\r\n    public enum ImageQuality\r\n    {\r\n        /// <summary>\r\n        /// Images of a small (typically icon) size.\r\n        /// </summary>\r\n        Small,\r\n\r\n        /// <summary>\r\n        /// Images suitable for high-res icons or at a stretch, large icon representation\r\n        /// </summary>\r\n        Medium,\r\n\r\n        /// <summary>\r\n        /// High definition/detail images\r\n        /// </summary>\r\n        Large,\r\n\r\n        /// <summary>\r\n        /// Let the system select the best available image\r\n        /// </summary>\r\n        BestAvailable,\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Images/ImageTypeRecord.cs",
    "content": "namespace Sentinel.Images\n{\n    using WpfExtras;\n\n    public class ImageTypeRecord : ViewModelBase\n    {\n        private string name;\n\n        private ImageQuality quality;\n\n        private string image;\n\n        public ImageTypeRecord(string name, ImageQuality quality, string image)\n        {\n            this.name = name;\n            this.quality = quality;\n            this.image = image;\n\n            DisplayName = name;\n        }\n\n        public string Name\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public ImageQuality Quality\n        {\n            get\n            {\n                return quality;\n            }\n\n            set\n            {\n                if (quality != value)\n                {\n                    quality = value;\n                    OnPropertyChanged(nameof(Quality));\n                }\n            }\n        }\n\n        public string Image\n        {\n            get\n            {\n                return image;\n            }\n\n            set\n            {\n                if (image != value)\n                {\n                    image = value;\n                    OnPropertyChanged(nameof(Image));\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/Interfaces/IAddTypeImage.cs",
    "content": "namespace Sentinel.Images.Interfaces\n{\n    public interface IAddTypeImage\n    {\n        void Add();\n    }\n}"
  },
  {
    "path": "Sentinel/Images/Interfaces/IEditTypeImage.cs",
    "content": "namespace Sentinel.Images.Interfaces\n{\n    public interface IEditTypeImage\n    {\n        void Edit(ImageTypeRecord imageTypeRecord);\n    }\n}"
  },
  {
    "path": "Sentinel/Images/Interfaces/IRemoveTypeImage.cs",
    "content": "namespace Sentinel.Images.Interfaces\n{\n    public interface IRemoveTypeImage\n    {\n        void Remove(ImageTypeRecord imageTypeRecord);\n    }\n}"
  },
  {
    "path": "Sentinel/Images/Interfaces/ITypeImageService.cs",
    "content": "﻿namespace Sentinel.Images.Interfaces\n{\n    using System.Collections.ObjectModel;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n\n    public interface ITypeImageService\n    {\n        ICommand Add { get; }\n\n        ICommand Edit { get; }\n\n        [DataMember]\n        ObservableCollection<ImageTypeRecord> ImageMappings { get; }\n\n        ICommand Remove { get; }\n\n        int SelectedIndex { get; set; }\n\n        void Register(string type, ImageQuality quality, string image);\n\n        ImageTypeRecord Get(string type, ImageOptions options);\n    }\n}"
  },
  {
    "path": "Sentinel/Images/Interfaces/ImageOptions.cs",
    "content": "namespace Sentinel.Images.Interfaces\n{\n    public class ImageOptions\n    {\n        public ImageQuality Quality { get; set; } = ImageQuality.BestAvailable;\n\n        public bool AcceptLowerQuality { get; set; } = true;\n\n        public bool ImageMustExist { get; set; } = false;\n    }\n}"
  },
  {
    "path": "Sentinel/Images/RemoveTypeImageMapping.cs",
    "content": "namespace Sentinel.Images\n{\n    using System.Windows;\n\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Services;\n\n    public class RemoveTypeImageMapping : IRemoveTypeImage\n    {\n        public void Remove(ImageTypeRecord typeImageRecord)\n        {\n            var service = ServiceLocator.Instance.Get<ITypeImageService>();\n\n            if (service != null)\n            {\n                var prompt = \"Are you sure you want to remove the selected image?\\r\\n\\r\\n\"\n                             + $\"Image Name = \\\"{typeImageRecord.Name}\\\"\";\n\n                var result = MessageBox.Show(\n                    prompt,\n                    \"Remove Image\",\n                    MessageBoxButton.YesNo,\n                    MessageBoxImage.Question,\n                    MessageBoxResult.No);\n\n                if (result == MessageBoxResult.Yes)\n                {\n                    service.ImageMappings.Remove(typeImageRecord);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Images/TypeToImageService.cs",
    "content": "namespace Sentinel.Images\n{\n    using System.Collections.ObjectModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Windows.Input;\n\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class TypeToImageService : ViewModelBase, ITypeImageService, IDefaultInitialisation\n    {\n        private int selectedIndex;\n\n        public TypeToImageService()\n        {\n            ImageMappings = new ObservableCollection<ImageTypeRecord>();\n\n            Add = new DelegateCommand(AddMapping);\n            Edit = new DelegateCommand(EditMapping, e => selectedIndex != -1);\n            Remove = new DelegateCommand(RemoveMapping, e => selectedIndex != -1);\n\n            AddImage = new AddTypeImageService();\n            EditImage = new EditTypeImageMapping();\n            RemoveImage = new RemoveTypeImageMapping();\n        }\n\n        public ICommand Add { get; private set; }\n\n        public ICommand Edit { get; private set; }\n\n        public ObservableCollection<ImageTypeRecord> ImageMappings { get; private set; }\n\n        public ICommand Remove { get; private set; }\n\n        public int SelectedIndex\n        {\n            get\n            {\n                return selectedIndex;\n            }\n\n            set\n            {\n                if (value != selectedIndex)\n                {\n                    selectedIndex = value;\n                    OnPropertyChanged(nameof(SelectedIndex));\n                    CommandManager.InvalidateRequerySuggested();\n                }\n            }\n        }\n\n        private IAddTypeImage AddImage { get; set; }\n\n        private IEditTypeImage EditImage { get; set; }\n\n        private IRemoveTypeImage RemoveImage { get; set; }\n\n        public void Register(string type, ImageQuality quality, string image)\n        {\n            Debug.Assert(quality != ImageQuality.BestAvailable, \"Must use a specific size when registering\");\n\n            var typeName = type.ToUpper();\n            var options = new ImageOptions { AcceptLowerQuality = false, Quality = quality };\n            var imageRecord = Get(typeName, options);\n\n            var updated = false;\n            if (imageRecord != null)\n            {\n                if (imageRecord.Image != image)\n                {\n                    imageRecord.Image = image;\n                    updated = true;\n                }\n            }\n            else\n            {\n                ImageMappings.Add(new ImageTypeRecord(typeName, quality, image));\n                updated = true;\n            }\n\n            if (updated)\n            {\n                OnPropertyChanged(nameof(ImageMappings));\n            }\n        }\n\n        public ImageTypeRecord Get(\n            string type,\n            ImageOptions options)\n        {\n            type.ThrowIfNullOrWhiteSpace(nameof(type));\n            options.ThrowIfNull(nameof(options));\n\n            var quality = options.Quality;\n\n            var typeName = type.ToUpper();\n            var sorted = ImageMappings.Where(r => r.Name == typeName).OrderByDescending(r => r.Quality);\n\n            if (quality == ImageQuality.BestAvailable)\n            {\n                return sorted.FirstOrDefault();\n            }\n\n            var exactMatch = sorted.SingleOrDefault(r => r.Quality == quality);\n            if (exactMatch != null)\n            {\n                return exactMatch;\n            }\n\n            // Don't have explicit size or have not asked for best available.\n            if (options.AcceptLowerQuality)\n            {\n                Debug.Assert(quality != ImageQuality.BestAvailable, \"Must be an explicit Quality\");\n                var newQuality = quality == ImageQuality.Large ? ImageQuality.Medium : ImageQuality.Small;\n                if (newQuality != quality)\n                {\n                    // Update options\n                    // TODO: ideally this should clone\n                    var newOptions = new ImageOptions\n                                         {\n                                             Quality = newQuality,\n                                             AcceptLowerQuality = options.AcceptLowerQuality,\n                                             ImageMustExist = options.ImageMustExist,\n                                         };\n\n                    // Recursive\n                    return Get(type, newOptions);\n                }\n            }\n\n            return options.ImageMustExist ? Get(\"Unknown\", options) : null;\n        }\n\n        public void Initialise()\n        {\n            Register(\"ERROR\", ImageQuality.Small, \"/Resources/Small/Error.png\");\n            Register(\"ERROR\", ImageQuality.Medium, \"/Resources/Medium/Error.png\");\n            Register(\"ERROR\", ImageQuality.Large, \"/Resources/Large/Error.png\");\n\n            Register(\"WARN\", ImageQuality.Small, \"/Resources/Small/Warning.png\");\n            Register(\"WARN\", ImageQuality.Medium, \"/Resources/Medium/Warning.png\");\n            Register(\"WARN\", ImageQuality.Large, \"/Resources/Large/Warning.png\");\n\n            Register(\"TRACE\", ImageQuality.Small, \"/Resources/Small/Trace.png\");\n            Register(\"TRACE\", ImageQuality.Medium, \"/Resources/Medium/Trace.png\");\n            Register(\"TRACE\", ImageQuality.Large, \"/Resources/Large/Trace.png\");\n\n            Register(\"DEBUG\", ImageQuality.Small, \"/Resources/Small/Debug.png\");\n            Register(\"DEBUG\", ImageQuality.Medium, \"/Resources/Medium/Debug.png\");\n            Register(\"DEBUG\", ImageQuality.Large, \"/Resources/Large/Debug.png\");\n\n            Register(\"INFO\", ImageQuality.Small, \"/Resources/Small/Info.png\");\n            Register(\"INFO\", ImageQuality.Medium, \"/Resources/Medium/Info.png\");\n            Register(\"INFO\", ImageQuality.Large, \"/Resources/Large/Info.png\");\n\n            Register(\"FATAL\", ImageQuality.Small, \"/Resources/Small/Fatal.png\");\n            Register(\"FATAL\", ImageQuality.Medium, \"/Resources/Medium/Fatal.png\");\n            Register(\"FATAL\", ImageQuality.Large, \"/Resources/Large/Fatal.png\");\n\n            Register(\"TIMING\", ImageQuality.Small, \"/Resources/Small/Clock.png\");\n\n            Register(\"UNKNOWN\", ImageQuality.Small, \"/Resources/Small/Unknown.png\");\n            Register(\"UNKNOWN\", ImageQuality.Medium, \"/Resources/Medium/Unknown.png\");\n            Register(\"UNKNOWN\", ImageQuality.Large, \"/Resources/Large/Unknown.png\");\n        }\n\n        private void AddMapping(object obj)\n        {\n            AddImage.Add();\n        }\n\n        private void EditMapping(object obj)\n        {\n            var typeImageRecord = ImageMappings.ElementAt(SelectedIndex);\n            if (typeImageRecord != null)\n            {\n                EditImage.Edit(typeImageRecord);\n            }\n        }\n\n        private void RemoveMapping(object obj)\n        {\n            var typeImageRecord = ImageMappings.ElementAt(SelectedIndex);\n            RemoveImage.Remove(typeImageRecord);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/CaseInsensitiveComparer.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System;\n    using System.Collections.Generic;\n\n    public class CaseInsensitiveComparer<T> : IEqualityComparer<T>\n    {\n        public bool Equals(T x, T y)\n        {\n            var stringX = x as string;\n\n            if (stringX != null)\n            {\n                return string.Compare(stringX, y as string, StringComparison.OrdinalIgnoreCase) == 0;\n            }\n\n            return x.Equals(y);\n        }\n\n        public int GetHashCode(T obj)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/CodeContracts/ThrowIfNull.cs",
    "content": "namespace Sentinel.Interfaces.CodeContracts\n{\n    using System;\n    using System.Diagnostics;\n    using System.Diagnostics.Contracts;\n    using System.Runtime.CompilerServices;\n\n    public static partial class CodeContractExtensions\n    {\n        [ContractAbbreviator]\n        [DebuggerStepThrough]\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static void ThrowIfNull([ValidatedNotNull] this object value, string parameterName)\n        {\n            Contract.Requires(\n                value != null,\n                \"The value '\" + parameterName + \"' cannot be null. \");\n\n            if (value == null)\n            {\n                throw new ArgumentNullException(parameterName);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/CodeContracts/ThrowIfNullOrWhitespace.cs",
    "content": "namespace Sentinel.Interfaces.CodeContracts\n{\n    using System;\n    using System.Diagnostics;\n    using System.Diagnostics.Contracts;\n    using System.Runtime.CompilerServices;\n\n    public static partial class CodeContractExtensions\n    {\n        [ContractAbbreviator]\n        [DebuggerStepThrough]\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static void ThrowIfNullOrWhiteSpace(\n            [ValidatedNotNull] this string value,\n            string parameterName)\n        {\n            Contract.Requires(!string.IsNullOrWhiteSpace(value));\n            if (string.IsNullOrWhiteSpace(value))\n            {\n                throw new ArgumentNullException(parameterName);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/CodeContracts/ValidatedNotNullAttribute.cs",
    "content": "namespace Sentinel.Interfaces.CodeContracts\n{\n    using System;\n\n    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]\n    public sealed class ValidatedNotNullAttribute : Attribute\n    {\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/CollectionChangeHelper.cs",
    "content": "﻿namespace Sentinel.Interfaces\n{\n    using System.Collections.Specialized;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using log4net;\n\n    public delegate string GetFriendlyNameDelegate<T>(T obj);\n\n    public class CollectionChangeHelper<T>\n    {\n        public event GetFriendlyNameDelegate<T> NameLookup;\n\n        public event PropertyChangedEventHandler OnPropertyChanged;\n\n        public string ManagerName { get; set; }\n\n        public void AttachDetach(object sender, NotifyCollectionChangedEventArgs e)\n        {\n            if (e.Action == NotifyCollectionChangedAction.Add)\n            {\n                foreach (var newItem in e.NewItems)\n                {\n                    Debug.Assert(newItem != null, \"New item to insert can not be null.\");\n                    Debug.Assert(newItem is T, \"New item to insert must be a \" + typeof(T));\n\n                    if (e.NewItems != null && newItem is INotifyPropertyChanged)\n                    {\n                        // Register on OnPropertyChanged.\n                        var notifyPropertyChanged = newItem as INotifyPropertyChanged;\n                        var t = (T)newItem;\n                        var name = NameLookup != null ? NameLookup(t) : \"<Unknown>\";\n\n                        var log = LogManager.GetLogger(\"ObservableCollection:\" + ManagerName);\n                        log.DebugFormat(\n                            \"{0} detected {1} added to collection and binding to its PropertyChanged event\",\n                            ManagerName,\n                            name);\n\n                        notifyPropertyChanged.PropertyChanged += OnPropertyChanged;\n                    }\n                }\n            }\n            else if (e.Action == NotifyCollectionChangedAction.Remove)\n            {\n                foreach (var oldItem in e.OldItems)\n                {\n                    Debug.Assert(oldItem != null, \"Item to remove can not be null.\");\n                    Debug.Assert(oldItem is T, \"Item to remove must be a \" + typeof(T));\n\n                    if (e.OldItems != null\n                        && oldItem is INotifyPropertyChanged)\n                    {\n                        // Unregister on OnPropertyChanged.\n                        var notifyPropertyChanged = oldItem as INotifyPropertyChanged;\n                        var t = (T)oldItem;\n                        var name = NameLookup != null ? NameLookup(t) : \"<Unknown>\";\n\n                        var log = LogManager.GetLogger(\"ObservableCollection:\" + ManagerName);\n                        log.DebugFormat(\n                            \"{0} detected {1} removed from collection and unbinding from its PropertyChanged event\",\n                            ManagerName,\n                            name);\n\n                        notifyPropertyChanged.PropertyChanged -= OnPropertyChanged;\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/EnumerableExtensions.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System.Collections.Generic;\n    using System.Linq;\n\n    public static class EnumerableExtensions\n    {\n        public static int IndexOf<T>(this IEnumerable<T> list, T value)\n        {\n            return list.IndexOf(value, null);\n        }\n\n        public static int IndexOf<T>(this IEnumerable<T> list, T value, IEqualityComparer<T> comparer)\n        {\n            comparer = comparer ?? EqualityComparer<T>.Default;\n            var found = list.Select((a, i) => new { a, i }).FirstOrDefault(x => comparer.Equals(x.a, value));\n            return found?.i ?? -1;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/IDefaultInitialisation.cs",
    "content": "﻿namespace Sentinel.Interfaces\r\n{\r\n    public interface IDefaultInitialisation\r\n    {\r\n        void Initialise();\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Interfaces/IHighlighterStyle.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System.Windows.Media;\n\n    public interface IHighlighterStyle\n    {\n        Color? Background { get; set; }\n\n        Color? Foreground { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/ILogEntry.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System;\n    using System.Collections.Generic;\n\n    public interface ILogEntry\n    {\n        /// <summary>\n        /// Gets or sets classification for the log entry.  Can be free-text but will typically\n        /// contain values like \"DEBUG\" or \"ERROR\".\n        /// </summary>\n        string Type { get; set; }\n\n        /// <summary>\n        /// Gets or sets Date/Time for the original log entry.\n        /// </summary>\n        DateTime DateTime { get; set; }\n\n        /// <summary>\n        /// Gets or sets the main body of the log entry.\n        /// </summary>\n        string Description { get; set; }\n\n        /// <summary>\n        /// Gets or sets source of the log entry, e.g. where it came from.\n        /// </summary>\n        string Source { get; set; }\n\n        /// <summary>\n        /// Gets or sets the system (e.g. machine) where this message came from.\n        /// </summary>\n        string System { get; set; }\n\n        /// <summary>\n        /// Gets or sets thread identifier for the source of the message.\n        /// </summary>\n        string Thread { get; set; }\n\n        /// <summary>\n        /// Dictionary of any meta-data that doesn't fit into the above values.\n        /// </summary>\n        Dictionary<string, object> MetaData { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/ILogger.cs",
    "content": "﻿namespace Sentinel.Interfaces\n{\n    using System.Collections.Generic;\n    using System.ComponentModel;\n\n    /// <summary>\n    /// Interface for a representation of a logger.\n    /// </summary>\n    public interface ILogger : INotifyPropertyChanged\n    {\n        /// <summary>\n        /// Gets the entries for the logger.\n        /// </summary>\n        IEnumerable<ILogEntry> Entries { get; }\n\n        /// <summary>\n        /// Gets or sets the name of the logger.\n        /// </summary>\n        string Name { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether new entries are added to the Entries collection.\n        /// </summary>\n        bool Enabled { get; set; }\n\n        /// <summary>\n        /// Gets the newly added entries for the logger.\n        /// </summary>\n        IEnumerable<ILogEntry> NewEntries { get; }\n\n        /// <summary>\n        /// Clear the log entries.\n        /// </summary>\n        void Clear();\n\n        /// <summary>\n        /// Add a batch of new messages to the logger.\n        /// </summary>\n        /// <param name=\"entries\">Ordered list/queue of items to add.</param>\n        void AddBatch(Queue<ILogEntry> entries);\n\n        /// <summary>\n        /// Allows a specific limit of messages to be applied.\n        /// Note, it is the responsibility of the appender to enforce limits.\n        /// </summary>\n        /// <param name=\"maximumMessages\">\n        /// Number representing the maximum number of entries required.\n        /// </param>\n        void LimitMessageCount(int maximumMessages);\n    }\n}\n"
  },
  {
    "path": "Sentinel/Interfaces/IUserPreferences.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System.Collections.Generic;\n    using System.Runtime.Serialization;\n\n    public interface IUserPreferences\n    {\n        string CurrentThemeName { get; }\n\n        IEnumerable<string> DateFormatOptions { get; }\n\n        IEnumerable<string> TimeFormatOptions { get; }\n\n        [DataMember]\n        int SelectedDateOption { get; set; }\n\n        [DataMember]\n        int SelectedTimeFormatOption { get; set; }\n\n        [DataMember]\n        bool ConvertUtcTimesToLocalTimeZone { get; set; }\n\n        [DataMember]\n        bool UseArrivalDateTime { get; set; }\n\n        [DataMember]\n        int SelectedTypeOption { get; set; }\n\n        bool Show { get; set; }\n\n        [DataMember]\n        bool ShowThreadColumn { get; set; }\n\n        [DataMember]\n        bool ShowExceptionColumn { get; set; }\n\n        [DataMember]\n        bool ShowSourceColumn { get; set; }\n\n        IEnumerable<string> TypeOptions { get; }\n\n        [DataMember]\n        bool UseLazyRebuild { get; set; }\n\n        [DataMember]\n        bool UseStackedLayout { get; set; }\n\n        [DataMember]\n        bool UseTighterRows { get; set; }\n\n        [DataMember]\n        bool DoubleClickToShowExceptions { get; set; }\n\n        [DataMember]\n        bool ShowSourceInformationColumns { get; set; }\n\n        [DataMember]\n        bool ShowContextColumn { get; set; }\n\n        [DataMember]\n        string ContextProperty { get; set; }\n\n        [DataMember]\n        bool EnableClearCommand { get; set; }\n\n        [DataMember]\n        string ClearCommandMatchText { get; set; }\n\n        [DataMember]\n        bool LimitMessages { get; set; }\n\n        // TODO: this should be an integer bound to a numeric control.\n        [DataMember]\n        string MaximumMessageCount { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/LinqHelpers.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System.Collections.Generic;\n\n    public static class LinqHelpers\n    {\n        public static IList<T> Swap<T>(this IList<T> list, int index1, int index2)\n        {\n            T temp = list[index1];\n            list[index1] = list[index2];\n            list[index2] = temp;\n            return list;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/LogEntryFields.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System;\n    using System.Runtime.Serialization;\n\n    using Newtonsoft.Json;\n    using Newtonsoft.Json.Converters;\n\n    /// <summary>\n    /// An enumeration of the possible fields within a LogEntry so that\n    /// a reference can be made to a specific property, for example,\n    /// when implementing a customisable filter, it will be possible to\n    /// indicate which field the filter applies to without using free-text\n    /// or multiple overloaded methods.\n    /// </summary>\n    [Flags]\n    [DataContract]\n    [JsonConverter(typeof(StringEnumConverter))]\n    public enum LogEntryFields\n    {\n        /// <summary>\n        /// Not a field enumeration, default.\n        /// </summary>\n        None = 0,\n\n        /// <summary>\n        /// Type field, e.g. ERROR, DEBUG.\n        /// </summary>\n        Type = 1,\n\n        /// <summary>\n        /// System field of the log entry, e.g. \"LogManager\" or \"ConfigurationService\"\n        /// </summary>\n        System = 2,\n\n        /// <summary>\n        /// Classification of log entry, used internally when messages get reclassified\n        /// or attributed from a specific source.\n        /// </summary>\n        Classification = 4,\n\n        /// <summary>\n        /// Thread identifier of original message.\n        /// </summary>\n        /// <remarks>\n        /// Not the most useful field but since this a string, not a number, it can\n        /// be used for overloaded behaviour in the future.\n        /// </remarks>\n        Thread = 8,\n\n        /// <summary>\n        /// Source field.\n        /// </summary>\n        Source = 16,\n\n        /// <summary>\n        /// Description field, this is usually the log message sent from the source.\n        /// </summary>\n        /// <remarks>\n        /// Some of the incoming classification mechanisms may have preprocessed\n        /// this field to retrieve and remove certain information so that the presented\n        /// description has been 'cracked' into constituent parts.\n        /// </remarks>\n        Description = 32,\n\n        /// <summary>\n        /// Host field of message, usually the originating machine's name.\n        /// </summary>\n        Host = 64,\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/MatchMode.cs",
    "content": "namespace Sentinel.Interfaces\n{\n    using System.Runtime.Serialization;\n\n    using Newtonsoft.Json;\n    using Newtonsoft.Json.Converters;\n\n    /// <summary>\n    /// Modes for matching strings.\n    /// </summary>\n    [DataContract]\n    [JsonConverter(typeof(StringEnumConverter))]\n    public enum MatchMode\n    {\n        /// <summary>\n        /// Exact case-sensitive match.\n        /// </summary>\n        Exact,\n\n        /// <summary>\n        /// Case-sensitive sub-string match.\n        /// </summary>\n        CaseSensitive,\n\n        /// <summary>\n        /// Case-insensitive sub-string match.\n        /// </summary>\n        CaseInsensitive,\n\n        /// <summary>\n        /// Regular expression matching.\n        /// </summary>\n        RegularExpression,\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/Providers/ILogProvider.cs",
    "content": "namespace Sentinel.Interfaces.Providers\n{\n    public interface ILogProvider\n    {\n        IProviderInfo Information { get; }\n\n        IProviderSettings ProviderSettings { get; }\n\n        ILogger Logger { get; set; }\n\n        string Name { get; set; }\n\n        bool IsActive { get; }\n\n        void Start();\n\n        void Pause();\n\n        void Close();\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/Providers/INetworkProvider.cs",
    "content": "﻿namespace Sentinel.Interfaces.Providers\n{\n    using System.Runtime.Serialization;\n\n    public interface INetworkProvider : ILogProvider\n    {\n        [DataMember]\n        int Port { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/Providers/IProviderInfo.cs",
    "content": "namespace Sentinel.Interfaces.Providers\n{\n    using System;\n    using System.Runtime.Serialization;\n\n    public interface IProviderInfo\n    {\n        [DataMember]\n        Guid Identifier { get; }\n\n        [DataMember]\n        string Name { get; }\n\n        [DataMember]\n        string Description { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/Providers/IProviderRegistrationRecord.cs",
    "content": "﻿namespace Sentinel.Interfaces.Providers\n{\n    using System;\n\n    public interface IProviderRegistrationRecord\n    {\n        Guid Identifier { get; }\n\n        IProviderInfo Info { get; }\n\n        Type Settings { get; }\n\n        Type Implementer { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Interfaces/Providers/IProviderSettings.cs",
    "content": "﻿namespace Sentinel.Interfaces.Providers\n{\n    using System.Runtime.Serialization;\n\n    public interface IProviderSettings\n    {\n        [DataMember]\n        string Name { get; }\n\n        [DataMember]\n        string Summary { get; }\n\n        /// <summary>\n        /// Gets reference back to the provider this setting is appropriate to.\n        /// </summary>\n        [DataMember]\n        IProviderInfo Info { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Log4Net/ConfigurationPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Log4Net.ConfigurationPage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n             xmlns:local=\"clr-namespace:Sentinel.Log4Net\"\n             xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"450\" d:DesignWidth=\"800\">\n\n    <UserControl.Resources>\n        <converters:BooleanInvertingValueConverter x:Key=\"InvertConverter\" />\n        <converters:VisibilityToHiddenConverter x:Key=\"visToHiddenConverter\" />\n    </UserControl.Resources>\n\n    <Grid Margin=\"3\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"40\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n        <StackPanel Grid.Row=\"0\"\n                    Grid.Column=\"1\"\n                    IsEnabled=\"False\"></StackPanel>\n        <TextBlock Text=\"Port : \"\n                   Margin=\"3\"\n                   Grid.Row=\"2\"\n                   VerticalAlignment=\"Center\" />\n        <TextBox x:Name=\"portText\"\n                 Margin=\"3\"\n                 Text=\"{Binding Port, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}\"\n                 Grid.Row=\"2\"\n                 Grid.Column=\"1\"\n                 VerticalAlignment=\"Center\" />\n        <TextBlock Margin=\"3\"\n                   Grid.Row=\"3\"\n                   Grid.Column=\"1\"\n                   VerticalAlignment=\"Top\"\n                   TextWrapping=\"WrapWithOverflow\">\n            <Run>Please note that depending upon your firewall settings, you may be prompted to confirm the opening of a network port.</Run>\n            <LineBreak />\n            <Run>This normally only occurs the first time.</Run>\n        </TextBlock>\n    </Grid>\n\n</UserControl>\n"
  },
  {
    "path": "Sentinel/Log4Net/ConfigurationPage.xaml.cs",
    "content": "﻿namespace Sentinel.Log4Net\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Windows.Controls;\n\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for NewConfigPage.xaml\n    /// </summary>\n    public partial class ConfigurationPage : UserControl, IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private readonly ReadOnlyObservableCollection<IWizardPage> readonlyChildren;\n\n        private bool isValid;\n\n        private int port;\n\n        public ConfigurationPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            readonlyChildren = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            // Register to self so that we can handler user interactions.\n            PropertyChanged += SelectProviderPagePropertyChanged;\n\n            Port = 9998;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public int Port\n        {\n            get\n            {\n                return port;\n            }\n\n            set\n            {\n                if (port != value)\n                {\n                    port = value;\n                    OnPropertyChanged(nameof(Port));\n                }\n            }\n        }\n\n        public string Title\n        {\n            get\n            {\n                return \"Configure Provider\";\n            }\n        }\n\n        public ReadOnlyObservableCollection<IWizardPage> Children\n        {\n            get\n            {\n                return readonlyChildren;\n            }\n        }\n\n        public string Description\n        {\n            get\n            {\n                return \"Network settings to be used by new provider\";\n            }\n        }\n\n        public bool IsValid\n        {\n            get\n            {\n                return isValid;\n            }\n\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent\n        {\n            get\n            {\n                return this;\n            }\n        }\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData != null, \"Expecting the save-data component to have details from the previous pages.\");\n            Debug.Assert(\n                saveData is IProviderSettings,\n                \"Expecting the save-data component to be of an IProviderSettings type.\");\n\n            var providerInfo = (IProviderSettings)saveData;\n            return new UdpAppenderSettings(providerInfo) { Port = Port };\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void SelectProviderPagePropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"Port\")\n            {\n                var state = port > 2000;\n                Trace.WriteLine(string.Format(\"Setting PageValidates to {0}\", state));\n                IsValid = state;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Log4Net/IUdpAppenderListenerSettings.cs",
    "content": "namespace Sentinel.Log4Net\n{\n    using Sentinel.Interfaces.Providers;\n\n    public interface IUdpAppenderListenerSettings : IProviderSettings\n    {\n        int Port { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Log4Net/Log4NetProvider.cs",
    "content": "﻿namespace Sentinel.Log4Net\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Net;\n    using System.Net.Sockets;\n    using System.Text;\n    using System.Threading;\n    using System.Threading.Tasks;\n    using System.Xml.Linq;\n    using log4net;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Interfaces.Providers;\n\n    public class Log4NetProvider : INetworkProvider\n    {\n        public static readonly IProviderRegistrationRecord ProviderRegistrationInformation =\n            new ProviderRegistrationInformation(new ProviderInfo());\n\n        private const int PumpFrequency = 100;\n\n        private const string ApacheNamespace = \"http://logging.apache.org/log4net/schemas/log4net-events-1.2/\";\n\n        private static readonly ILog Log = LogManager.GetLogger(typeof(Log4NetProvider));\n\n        private readonly Queue<string> pendingQueue = new Queue<string>();\n\n        private readonly IUdpAppenderListenerSettings udpSettings;\n\n        private readonly XNamespace log4NetNamespace = \"unique\";\n\n        private readonly XNamespace apacheNamespace = ApacheNamespace;\n\n        private CancellationTokenSource cancellationTokenSource;\n\n        private Task udpListenerTask;\n\n        private Task messagePumpTask;\n\n        public Log4NetProvider(IProviderSettings settings)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            udpSettings = settings as IUdpAppenderListenerSettings;\n            udpSettings.ThrowIfNull(nameof(udpSettings));\n\n            Information = ProviderRegistrationInformation.Info;\n            ProviderSettings = udpSettings;\n        }\n\n        public IProviderInfo Information { get; private set; }\n\n        public IProviderSettings ProviderSettings { get; private set; }\n\n        public ILogger Logger { get; set; }\n\n        public string Name { get; set; }\n\n        public bool IsActive => udpListenerTask != null && udpListenerTask.Status == TaskStatus.Running;\n\n        public int Port { get; private set; }\n\n        public void Start()\n        {\n            Log.Debug(\"Start requested\");\n\n            if (udpListenerTask == null || udpListenerTask.IsCompleted)\n            {\n                cancellationTokenSource = new CancellationTokenSource();\n                var token = cancellationTokenSource.Token;\n\n                udpListenerTask = Task.Factory.StartNew(SocketListener, token);\n                messagePumpTask = Task.Factory.StartNew(MessagePump, token);\n            }\n            else\n            {\n                Log.Warn(\"UDP listener task is already active and can not be started again.\");\n            }\n        }\n\n        public void Pause()\n        {\n            Log.Debug(\"Pause requested\");\n            if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)\n            {\n                Log.Debug(\"Cancellation token triggered\");\n                cancellationTokenSource.Cancel();\n            }\n        }\n\n        public void Close()\n        {\n            Log.Debug(\"Close requested\");\n            if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)\n            {\n                Log.Debug(\"Cancellation token triggered\");\n                cancellationTokenSource.Cancel();\n            }\n        }\n\n        private void SocketListener()\n        {\n            Log.Debug(\"SocketListener started\");\n\n            if (udpSettings == null)\n            {\n                Log.Error(\"UDP settings has not been initialized\");\n                throw new NullReferenceException();\n            }\n\n            while (!cancellationTokenSource.IsCancellationRequested)\n            {\n                var endPoint = new IPEndPoint(IPAddress.Any, udpSettings.Port);\n\n                using (var listener = new UdpClient(endPoint))\n                {\n                    var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);\n                    listener.Client.ReceiveTimeout = 1000;\n\n                    while (!cancellationTokenSource.IsCancellationRequested)\n                    {\n                        try\n                        {\n                            var bytes = listener.Receive(ref remoteEndPoint);\n\n                            if (Log.IsDebugEnabled)\n                                Log.DebugFormat(\"Received {0} bytes from {1}\", bytes.Length, remoteEndPoint.Address);\n\n                            var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length);\n                            lock (pendingQueue)\n                            {\n                                pendingQueue.Enqueue(message);\n                            }\n                        }\n                        catch (SocketException socketException)\n                        {\n                            if (socketException.SocketErrorCode != SocketError.TimedOut)\n                            {\n                                Log.Error(\"SocketException\", socketException);\n                                Log.DebugFormat(\n                                    \"SocketException.SocketErrorCode = {0}\",\n                                    socketException.SocketErrorCode);\n\n                                // Break out of the 'using socket' loop and try to establish a new socket.\n                                break;\n                            }\n                        }\n                        catch (Exception e)\n                        {\n                            Log.Error(\"UdpClient Exception\", e);\n                        }\n                    }\n                }\n            }\n\n            Log.Debug(\"SocketListener completed\");\n        }\n\n        private void MessagePump()\n        {\n            Log.Debug(\"MessagePump started\");\n\n            var processedQueue = new Queue<ILogEntry>();\n\n            while (!cancellationTokenSource.IsCancellationRequested)\n            {\n                Thread.Sleep(PumpFrequency);\n\n                try\n                {\n                    if (Logger != null)\n                    {\n                        lock (pendingQueue)\n                        {\n                            while (pendingQueue.Count > 0)\n                            {\n                                var message = pendingQueue.Dequeue();\n\n                                // TODO: validate\n                                if (IsValidMessage(message))\n                                {\n                                    var deserializedMessage = DeserializeMessage(message);\n\n                                    if (deserializedMessage != null)\n                                    {\n                                        processedQueue.Enqueue(deserializedMessage);\n                                    }\n                                }\n                            }\n                        }\n\n                        if (processedQueue.Any())\n                        {\n                            Logger.AddBatch(processedQueue);\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    Log.Error(\"MessagePump Exception\", e);\n                }\n                finally\n                {\n                    processedQueue.Clear();\n                }\n            }\n\n            Log.Debug(\"MessagePump completed\");\n        }\n\n        private ILogEntry DeserializeMessage(string message)\n        {\n            try\n            {\n                // Record the current date/time\n                var receivedTime = DateTime.UtcNow;\n\n                var payload = $@\"<entry xmlns:log4net=\"\"{log4NetNamespace}\"\">{message}</entry>\";\n                var element = XElement.Parse(payload);\n\n                var eventNamespace = payload.Contains(ApacheNamespace) ? apacheNamespace : log4NetNamespace;\n\n                var @event = element.Element(eventNamespace + \"event\");\n\n                // Establish whether a sub-system seems to be defined.\n                if (@event != null)\n                {\n                    var description = @event.Element(eventNamespace + \"message\")?.Value;\n\n                    var classification = string.Empty;\n                    var system = @event.GetAttribute(\"logger\", string.Empty);\n                    var type = @event.GetAttribute(\"level\", string.Empty);\n                    var host = string.Empty;\n                    var props = new Dictionary<string, object>();\n                    foreach (var propertyElement in @event.Element(eventNamespace + \"properties\")?.Elements() ?? Enumerable.Empty<XElement>())\n                    {\n                        if (propertyElement.Name == eventNamespace + \"data\")\n                        {\n                            var name = propertyElement.GetAttribute(\"name\", string.Empty);\n                            var value = propertyElement.GetAttribute(\"value\", string.Empty);\n\n                            switch (name)\n                            {\n                                case \"log4net:HostName\":\n                                    host = value;\n                                    break;\n                                default:\n                                    if (props.ContainsKey(name))\n                                    {\n                                        props[name] = value;\n                                    }\n                                    else\n                                    {\n                                        props.Add(name, value);\n                                    }\n\n                                    break;\n                            }\n                        }\n                    }\n\n                    var className = string.Empty;\n                    var methodName = string.Empty;\n                    var sourceFile = string.Empty;\n                    var line = string.Empty;\n\n                    // Any source information\n                    var source = @event.Element(eventNamespace + \"locationInfo\");\n                    if (source != null)\n                    {\n                        className = source.Attribute(\"class\")?.Value;\n                        methodName = source.Attribute(\"method\")?.Value;\n                        sourceFile = source.Attribute(\"file\")?.Value;\n                        line = source.Attribute(\"line\")?.Value;\n                    }\n\n                    var metaData = new Dictionary<string, object>\n                    {\n                        [\"Classification\"] = classification,\n                        [\"Host\"] = host,\n                    };\n\n                    foreach (var prop in props)\n                    {\n                        if (metaData.ContainsKey(prop.Key))\n                        {\n                            Log.Warn($\"Already have property of {prop.Key}, overwriting\");\n                            metaData[prop.Key] = prop.Value;\n                        }\n                        else\n                        {\n                            metaData.Add(prop.Key, prop.Value);\n                        }\n                    }\n\n                    AddExceptionIfFound(@event, metaData, eventNamespace);\n\n                    // Extract from the source the originating date/time\n                    var sourceTime = @event.GetAttributeDateTime(\"timestamp\", DateTime.Now);\n\n                    var logEntry = new LogEntry\n                                       {\n                                           DateTime = sourceTime,\n                                           System = system,\n                                           Thread = @event.GetAttribute(\"thread\", string.Empty),\n                                           Description = description,\n                                           Type = type,\n                                           MetaData = metaData,\n                                       };\n\n                    // Determine whether this constitutes an exception\n                    var throwable = @event.Element(eventNamespace + \"throwable\");\n                    if (throwable != null)\n                    {\n                        logEntry.MetaData.Add(\"Exception\", throwable.Value);\n                    }\n                    else if (logEntry.Description.ToUpper().Contains(\"EXCEPTION\"))\n                    {\n                        logEntry.MetaData.Add(\"Exception\", true);\n                    }\n\n                    if (!string.IsNullOrWhiteSpace(className))\n                    {\n                        // TODO: use an object for these?\n                        logEntry.MetaData.Add(\"ClassName\", className);\n                        logEntry.MetaData.Add(\"MethodName\", methodName);\n                        logEntry.MetaData.Add(\"SourceFile\", sourceFile);\n                        logEntry.MetaData.Add(\"SourceLine\", line);\n                    }\n\n                    logEntry.MetaData.Add(\"ReceivedTime\", receivedTime);\n\n                    return logEntry;\n                }\n            }\n            catch (Exception e)\n            {\n                Log.Error(\"DeserializeMessage: exception when processing incoming message\", e);\n            }\n\n            return null;\n        }\n\n        private void AddExceptionIfFound(XElement entryEvent, Dictionary<string, object> metaData, XNamespace @namespace)\n        {\n            entryEvent.ThrowIfNull(nameof(entryEvent));\n\n            metaData.ThrowIfNull(nameof(metaData));\n\n            var exceptionElement = entryEvent.Element(@namespace + \"exception\");\n            if (exceptionElement != null)\n            {\n                metaData[\"Exception\"] = exceptionElement.Value;\n            }\n        }\n\n        private bool IsValidMessage(string message)\n        {\n            if (string.IsNullOrWhiteSpace(message))\n            {\n                throw new ArgumentNullException(nameof(message));\n            }\n\n            return message.StartsWith(\"<log4net:event\");\n        }\n\n        internal class ProviderInfo : IProviderInfo\n        {\n            public Guid Identifier => new Guid(\"D19E8097-FC08-47AF-8418-F737168A9645\");\n\n            public string Name => \"Log4Net UdpAppender Provider\";\n\n            public string Description => \"Handler for the remote side of log4net's UdpAppender\";\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Log4Net/LogEntry.cs",
    "content": "namespace Sentinel.Log4Net\n{\n    using System;\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n\n    internal class LogEntry : ILogEntry\n    {\n        public LogEntry()\n        {\n            Type = \"DEBUG\";\n            DateTime = DateTime.Now;\n            Description = \"Fake Message\";\n            Source = \"Fake source\";\n            System = \"System\";\n            Thread = \"123\";\n        }\n\n        /// <summary>\n        /// Classification for the log entry.  Can be free-text but will typically\n        /// contain values like \"DEBUG\" or \"ERROR\".\n        /// </summary>\n        public string Type { get; set; }\n\n        /// <summary>\n        /// Date/Time for the original log entry.\n        /// </summary>\n        public DateTime DateTime { get; set; }\n\n        /// <summary>\n        /// The main body of the log entry.\n        /// </summary>\n        public string Description { get; set; }\n\n        /// <summary>\n        /// Source of the log entry, e.g. where it came from.\n        /// </summary>\n        public string Source { get; set; }\n\n        /// <summary>\n        /// The system (e.g. machine) where this message came from.\n        /// </summary>\n        public string System { get; set; }\n\n        /// <summary>\n        /// Thread identifier for the source of the message.\n        /// </summary>\n        public string Thread { get; set; }\n\n        /// <summary>\n        /// Dictionary of any meta-data that doesn't fit into the above values.\n        /// </summary>\n        public Dictionary<string, object> MetaData { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Log4Net/ProviderRegistrationInformation.cs",
    "content": "namespace Sentinel.Log4Net\n{\n    using System;\n\n    using Sentinel.Interfaces.Providers;\n\n    public class ProviderRegistrationInformation : IProviderRegistrationRecord\n    {\n        public ProviderRegistrationInformation(IProviderInfo providerInfo)\n        {\n            Info = providerInfo;\n        }\n\n        public Guid Identifier => Info.Identifier;\n\n        public IProviderInfo Info { get; private set; }\n\n        public Type Settings => typeof(ConfigurationPage);\n\n        public Type Implementer => typeof(Log4NetProvider);\n    }\n}"
  },
  {
    "path": "Sentinel/Log4Net/UdpAppenderSettings.cs",
    "content": "namespace Sentinel.Log4Net\n{\n    using Sentinel.Interfaces.Providers;\n\n    public class UdpAppenderSettings : IUdpAppenderListenerSettings\n    {\n        public UdpAppenderSettings()\n        {\n            Name = \"Log4net UDP Appender\";\n            Info = Log4NetProvider.ProviderRegistrationInformation.Info;\n        }\n\n        public UdpAppenderSettings(IProviderSettings providerInfo)\n        {\n            Name = providerInfo.Name;\n            Info = providerInfo.Info;\n        }\n\n        public string Name { get; set; }\n\n        public string Summary => $\"{Name}: Listens on port {Port}\";\n\n        public IProviderInfo Info { get; set; }\n\n        public int Port { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Log4Net/XElementHelpers.cs",
    "content": "namespace Sentinel.Log4Net\n{\n    using System;\n    using System.Globalization;\n    using System.Xml.Linq;\n\n    using log4net;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public static class XElementHelpers\n    {\n        private static readonly ILog Log = LogManager.GetLogger(\"XElementHelpers\");\n\n        public static string GetAttribute(this XElement element, string attributeName, string defaultValue)\n        {\n            element.ThrowIfNull(nameof(element));\n\n            if (!element.HasAttributes)\n            {\n                return defaultValue;\n            }\n\n            var value = element.Attribute(attributeName);\n            return value?.Value ?? defaultValue;\n        }\n\n        public static DateTime GetAttributeDateTime(this XElement element, string attributeName, DateTime defaultValue)\n        {\n            var value = element.GetAttribute(attributeName, string.Empty);\n\n            var result = defaultValue;\n            if (!string.IsNullOrWhiteSpace(value))\n            {\n                if (!DateTime.TryParse(value, null, DateTimeStyles.AdjustToUniversal, out result))\n                {\n                    Log.Warn($\"Unable to parse DateTime of '{value}' to a valid date\");\n                }\n            }\n\n            return result;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Logger/ILogViewerDetails.cs",
    "content": "namespace Sentinel.Logger\n{\n    public interface ILogViewerDetails\n    {\n        string LogViewerName { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Logger/IUdpLogViewer.cs",
    "content": "namespace Sentinel.Logger\n{\n    using Sentinel.Views.Interfaces;\n\n    public interface IUdpLogViewer : ILogViewer\n    {\n        int Port { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Logger/LogWriter.cs",
    "content": "namespace Sentinel.Logger\r\n{\r\n    using System;\r\n    using System.Collections.Generic;\r\n    using System.IO;\r\n    using System.Windows;\r\n    using Sentinel.Interfaces;\r\n\r\n    /// <summary>\r\n    /// This service provider permits the writing of log entries to\r\n    /// a text file.\r\n    /// </summary>\r\n    public class LogWriter\r\n    {\r\n        /// <summary>\r\n        /// Register the current fields to a default text file.\r\n        /// </summary>\r\n        /// <param name=\"entries\">Entries to write to text file.</param>\r\n        public void Write(IEnumerable<ILogEntry> entries)\r\n        {\r\n            try\r\n            {\r\n                using (TextWriter tw = File.CreateText(\"log\"))\r\n                {\r\n                    foreach (var entry in entries)\r\n                    {\r\n                        tw.WriteLine(\r\n                            \"{0} {1} [{2}] {3}\",\r\n                            entry.DateTime,\r\n                            entry.Type,\r\n                            entry.System,\r\n                            entry.Description);\r\n                    }\r\n                }\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                MessageBox.Show(\r\n                    \"Caught an exception when writing:\\r\\n\\r\\n\" + e.Message,\r\n                    \"Log Writing Error\",\r\n                    MessageBoxButton.OK,\r\n                    MessageBoxImage.Error);\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Logs/Gui/AddNewLoggerWelcomePage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Logs.Gui.AddNewLoggerWelcomePage\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \r\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \r\n             mc:Ignorable=\"d\" \r\n             d:DesignHeight=\"300\" d:DesignWidth=\"300\">\r\n    <Grid>\r\n        <TextBlock TextWrapping=\"WrapWithOverflow\"\r\n                   Margin=\"10\">\r\n            <Run>Sentinel works with the concept of Logs, Providers and Displayers.</Run><LineBreak />\r\n            <LineBreak />\r\n            <Run FontWeight=\"Bold\">Logs</Run><Run xml:space=\"preserve\"> are predominantly non-visual elements but they are essential for the way Sentinel operates.  They are both the destination for </Run><Run FontWeight=\"Bold\">Providers</Run><Run xml:space=\"preserve\"> and the source for </Run><Run FontWeight=\"Bold\">Displayers</Run><Run>.</Run>\r\n            <LineBreak />\r\n            <LineBreak />\r\n            <Run xml:space=\"preserve\">Sentinel supports multiple providers for an individual log and multiple views upon an individual log.</Run>\r\n            <LineBreak />\r\n            <LineBreak />\r\n            <Run xml:space=\"preserve\">This wizard allows you define a new log and its associated providers and views.</Run>\r\n            <LineBreak />\r\n            <LineBreak />\r\n        </TextBlock>\r\n    </Grid>\r\n</UserControl>\r\n"
  },
  {
    "path": "Sentinel/Logs/Gui/AddNewLoggerWelcomePage.xaml.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Windows.Controls;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for AddNewLoggerPage.xaml.\n    /// </summary>\n    public partial class AddNewLoggerWelcomePage : IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private string description;\n\n        private string title;\n\n        public AddNewLoggerWelcomePage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            Title = \"Sentinel Logs\";\n            Description = \"Information about how Sentinel works with loggers, providers and views\";\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public string Title\n        {\n            get\n            {\n                return title;\n            }\n\n            private set\n            {\n                if (title != value)\n                {\n                    title = value;\n                    OnPropertyChanged(nameof(Title));\n                }\n            }\n        }\n\n        public string Description\n        {\n            get\n            {\n                return description;\n            }\n\n            private set\n            {\n                if (description != value)\n                {\n                    description = value;\n                    OnPropertyChanged(nameof(Description));\n                }\n            }\n        }\n\n        public bool IsValid => true;\n\n        public Control PageContent => this;\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public object Save(object saveData)\n        {\n            return saveData;\n        }\n\n        /// <summary>\n        ///   Raises this object's PropertyChanged event.\n        /// </summary>\n        /// <param name = \"propertyName\">The property that has a new value.</param>\n        private void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Logs/Gui/NewLoggerSettings.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\r\n{\r\n    using System.Collections.ObjectModel;\r\n    using System.Collections.Specialized;\r\n    using System.Linq;\r\n    using System.Text;\r\n\r\n    using Sentinel.Providers.Interfaces;\r\n    using Sentinel.Services;\r\n    using Sentinel.Views.Interfaces;\r\n\r\n    using WpfExtras;\r\n\r\n    public class NewLoggerSettings : ViewModelBase\r\n    {\r\n        private readonly InternalSettings settings = new InternalSettings();\r\n\r\n        private ObservableCollection<PendingProviderRecord> providers;\r\n\r\n        private string secondaryView;\r\n\r\n        public NewLoggerSettings()\r\n        {\r\n            Providers = new ObservableCollection<PendingProviderRecord>();\r\n            Views = new ObservableCollection<string>();\r\n\r\n            Views.CollectionChanged += ViewsCollectionChanged;\r\n        }\r\n\r\n        public bool IsVertical\r\n        {\r\n            get\r\n            {\r\n                return settings.IsVertical;\r\n            }\r\n\r\n            set\r\n            {\r\n                if (IsVertical != value)\r\n                {\r\n                    settings.IsVertical = value;\r\n                    OnPropertyChanged(nameof(IsVertical));\r\n                }\r\n            }\r\n        }\r\n\r\n        public string Layout\r\n        {\r\n            get\r\n            {\r\n                return settings.Layout;\r\n            }\r\n\r\n            set\r\n            {\r\n                if (Layout != value)\r\n                {\r\n                    settings.Layout = value;\r\n                    OnPropertyChanged(nameof(Layout));\r\n                }\r\n            }\r\n        }\r\n\r\n        public string LogName\r\n        {\r\n            get\r\n            {\r\n                return settings.LogName;\r\n            }\r\n\r\n            set\r\n            {\r\n                if (LogName != value)\r\n                {\r\n                    settings.LogName = value;\r\n                    OnPropertyChanged(nameof(LogName));\r\n                }\r\n            }\r\n        }\r\n\r\n        public ObservableCollection<PendingProviderRecord> Providers\r\n        {\r\n            get\r\n            {\r\n                return providers;\r\n            }\r\n\r\n            private set\r\n            {\r\n                if (providers != value)\r\n                {\r\n                    providers = value;\r\n                    OnPropertyChanged(nameof(Providers));\r\n                }\r\n            }\r\n        }\r\n\r\n        public string PrimaryView\r\n        {\r\n            get\r\n            {\r\n                return settings.PrimaryView;\r\n            }\r\n\r\n            private set\r\n            {\r\n                if (PrimaryView != value)\r\n                {\r\n                    settings.PrimaryView = value;\r\n                    OnPropertyChanged(nameof(PrimaryView));\r\n                }\r\n            }\r\n        }\r\n\r\n        public string SecondaryView\r\n        {\r\n            get\r\n            {\r\n                return secondaryView;\r\n            }\r\n\r\n            private set\r\n            {\r\n                if (secondaryView != value)\r\n                {\r\n                    secondaryView = value;\r\n                    OnPropertyChanged(nameof(SecondaryView));\r\n                }\r\n            }\r\n        }\r\n\r\n        public ObservableCollection<string> Views { get; set; }\r\n\r\n        public string ProviderSummary\r\n        {\r\n            get\r\n            {\r\n                var sb = new StringBuilder();\r\n\r\n                if (Providers != null && Providers.Count > 0)\r\n                {\r\n                    for (var index = 0; index < Providers.Count; index++)\r\n                    {\r\n                        var p = Providers[index];\r\n                        sb.Append($\"{p.Settings.Name} - {p.Settings.Info.Name} - {p.Settings.Summary}\");\r\n\r\n                        if (index < (providers.Count - 1))\r\n                        {\r\n                            sb.AppendLine();\r\n                        }\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    sb.Append(\"No providers configured.\");\r\n                }\r\n\r\n                return sb.ToString();\r\n            }\r\n        }\r\n\r\n        private static string LookupViewInformation(string identifier)\r\n        {\r\n            var vm = ServiceLocator.Instance.Get<IViewManager>();\r\n            var info = vm.Get(identifier);\r\n            return info.Name;\r\n        }\r\n\r\n        private void ViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)\r\n        {\r\n            if (Views.Count >= 1)\r\n            {\r\n                PrimaryView = LookupViewInformation(Views.ElementAt(0));\r\n            }\r\n\r\n            SecondaryView = Views.Count >= 2 ? LookupViewInformation(Views.ElementAt(1)) : \"Not used.\";\r\n        }\r\n\r\n        private class InternalSettings\r\n        {\r\n            public string LogName { get; set; }\r\n\r\n            public string PrimaryView { get; set; }\r\n\r\n            public bool IsVertical { get; set; }\r\n\r\n            public string Layout { get; set; }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Logs/Gui/NewLoggerSummaryPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Logs.Gui.NewLoggerSummaryPage\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \r\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \r\n             mc:Ignorable=\"d\" \r\n             d:DesignHeight=\"300\" d:DesignWidth=\"300\">\r\n    <Grid>\r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition Width=\"Auto\"\r\n                              SharedSizeGroup=\"Label\" />\r\n            <ColumnDefinition Width=\"*\"\r\n                              SharedSizeGroup=\"Content\" />\r\n        </Grid.ColumnDefinitions>\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"*\" />\r\n        </Grid.RowDefinitions>\r\n\r\n        <TextBlock Margin=\"3\"\r\n                   Grid.ColumnSpan=\"2\">\r\n            The following selections were made for the new log:\r\n        </TextBlock>\r\n        <GroupBox Header=\"Identifier\"\r\n                  Grid.Row=\"1\"\r\n                  Grid.ColumnSpan=\"2\"\r\n                  Grid.IsSharedSizeScope=\"True\">\r\n            <Grid>\r\n                <Grid.ColumnDefinitions>\r\n                    <ColumnDefinition SharedSizeGroup=\"Label\" />\r\n                    <ColumnDefinition SharedSizeGroup=\"Content\" />\r\n                </Grid.ColumnDefinitions>\r\n\r\n                <TextBlock Grid.Column=\"0\"\r\n                           Margin=\"3\"\r\n                           Text=\"Logger Name :\" />\r\n                <TextBlock Grid.Column=\"1\"\r\n                           Margin=\"3\"\r\n                           FontWeight=\"Bold\"\r\n                           Text=\"{Binding Settings.LogName}\" />\r\n            </Grid>\r\n        </GroupBox>\r\n        <GroupBox Header=\"Providers\"\r\n                  Grid.Row=\"2\"\r\n                  Grid.ColumnSpan=\"2\"\r\n                  Grid.IsSharedSizeScope=\"True\">\r\n            <Grid>\r\n                <Grid.ColumnDefinitions>\r\n                    <ColumnDefinition SharedSizeGroup=\"Label\" />\r\n                    <ColumnDefinition SharedSizeGroup=\"Content\" />\r\n                </Grid.ColumnDefinitions>\r\n                <TextBlock Grid.Column=\"0\"\r\n                           Grid.ColumnSpan=\"2\"\r\n                           Margin=\"3\"\r\n                           FontWeight=\"Bold\"\r\n                           TextWrapping=\"WrapWithOverflow\"\r\n                           Text=\"{Binding Settings.ProviderSummary}\" />\r\n            </Grid>\r\n        </GroupBox>\r\n        <GroupBox Header=\"Views\"\r\n                  Grid.Row=\"3\"\r\n                  Grid.ColumnSpan=\"2\"\r\n                  Grid.IsSharedSizeScope=\"True\">\r\n            <Grid>\r\n                <Grid.RowDefinitions>\r\n                    <RowDefinition />\r\n                    <RowDefinition Height=\"Auto\" />\r\n                    <RowDefinition Height=\"Auto\" />\r\n                </Grid.RowDefinitions>\r\n                <Grid.ColumnDefinitions>\r\n                    <ColumnDefinition SharedSizeGroup=\"Label\" />\r\n                    <ColumnDefinition SharedSizeGroup=\"Content\" />\r\n                </Grid.ColumnDefinitions>\r\n                <TextBlock Grid.Column=\"0\"\r\n                           Grid.ColumnSpan=\"2\"\r\n                           Margin=\"3\"\r\n                           Text=\"{Binding Settings.SingleView}\" />\r\n                <TextBlock Grid.Column=\"0\"\r\n                           Grid.ColumnSpan=\"2\"\r\n                           Margin=\"3\"\r\n                           Text=\"{Binding Settings.MultipleView}\" />\r\n                <TextBlock Grid.Row=\"1\"\r\n                           Grid.Column=\"0\"\r\n                           Margin=\"3\"\r\n                           Text=\"Primary View : \" />\r\n                <TextBlock Grid.Row=\"1\"\r\n                           Grid.Column=\"1\"\r\n                           Margin=\"3\"\r\n                           FontWeight=\"Bold\"\r\n                           Text=\"{Binding Settings.PrimaryView}\" />\r\n                <TextBlock Grid.Row=\"2\"\r\n                           Grid.Column=\"0\"\r\n                           Margin=\"3\"\r\n                           Text=\"Secondary View : \" />\r\n                <TextBlock Grid.Row=\"2\"\r\n                           Grid.Column=\"1\"\r\n                           Margin=\"3\"\r\n                           FontWeight=\"Bold\"\r\n                           Text=\"{Binding Settings.SecondaryView}\" />\r\n            </Grid>\r\n        </GroupBox>\r\n    </Grid>\r\n</UserControl>\r\n"
  },
  {
    "path": "Sentinel/Logs/Gui/NewLoggerSummaryPage.xaml.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Windows.Controls;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for NewLoggerSummaryPage.xaml.\n    /// </summary>\n    public partial class NewLoggerSummaryPage : IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        public NewLoggerSummaryPage()\n        {\n            InitializeComponent();\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            DataContext = this;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public string Title => \"Summary\";\n\n        public string Description => \"Review the selections made in this Wizard.\";\n\n        public bool IsValid => true;\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public Control PageContent => this;\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n        }\n\n        public object Save(object saveData)\n        {\n            return saveData;\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Logs/Gui/NewLoggerWizard.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\n{\n    using System.Diagnostics;\n    using System.Windows;\n\n    using WpfExtras;\n\n    public class NewLoggerWizard\n    {\n        public NewLoggerWizard()\n        {\n            Settings = new NewLoggerSettings();\n        }\n\n        public NewLoggerSettings Settings { get; private set; }\n\n        public bool Display(Window parent)\n        {\n            var wizard = new Wizard\n                             {\n                                 Owner = parent,\n                                 ShowNavigationTree = false,\n                                 Title = \"Sentinel - Add new logger\",\n                                 SavedData = Settings,\n                             };\n\n            wizard.AddPage(new AddNewLoggerWelcomePage());\n            wizard.AddPage(new SetLoggerNamePage());\n            wizard.AddPage(new ProvidersPage());\n            wizard.AddPage(new ViewSelectionPage());\n\n            var dialogResult = wizard.ShowDialog();\n            if (dialogResult == true)\n            {\n                Settings = wizard.SavedData as NewLoggerSettings;\n                Debug.Assert(Settings != null, \"Settings should be non-null and of NewLoggerSettings type\");\n            }\n\n            return dialogResult ?? false;\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Logs/Gui/ProvidersPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Logs.Gui.ProvidersPage\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\r\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\r\n             mc:Ignorable=\"d\"\r\n             d:DesignHeight=\"300\"\r\n             d:DesignWidth=\"500\">\r\n\t<UserControl.Resources>\r\n        <Style x:Key=\"listviewError\"\r\n               TargetType=\"{x:Type ListView}\">\r\n            <Style.Triggers>\r\n                <Trigger Property=\"Validation.HasError\"\r\n                         Value=\"true\">\r\n                    <Setter Property=\"ToolTip\"\r\n                            Value=\"{Binding RelativeSource={RelativeSource Self},\r\n                                            Path=(Validation.Errors).CurrentItem.ErrorContent}\" />\r\n                </Trigger>\r\n            </Style.Triggers>\r\n        </Style>\r\n    </UserControl.Resources>\r\n\t<Grid Margin=\"4\"\r\n          Loaded=\"PageLoaded\">\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition />\r\n            <RowDefinition />\r\n        </Grid.RowDefinitions>\r\n\r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition />\r\n            <ColumnDefinition Width=\"Auto\" />\r\n        </Grid.ColumnDefinitions>\r\n\r\n        <ListView Grid.Column=\"0\"\r\n                  Grid.Row=\"1\"\r\n                  Grid.RowSpan=\"2\"\r\n                  Style=\"{StaticResource listviewError}\"\r\n                  ItemsSource=\"{Binding Providers, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}\"\r\n                  SelectedIndex=\"{Binding SelectedProviderIndex, Mode=OneWayToSource}\"\r\n                  SelectionMode=\"Single\">\r\n            <ListView.View>\r\n                <GridView AllowsColumnReorder=\"False\">\r\n                    <GridView.Columns>\r\n                        <GridViewColumn Header=\"Name\"\r\n                                        DisplayMemberBinding=\"{Binding Path=Settings.Name}\" \r\n                                        Width=\"50\"/>\r\n                        <GridViewColumn Header=\"Type\"\r\n                                        DisplayMemberBinding=\"{Binding Path=Info.Name}\" \r\n                                        Width=\"120\"/>\r\n                        <GridViewColumn Header=\"Configuration\"\r\n                                        DisplayMemberBinding=\"{Binding Path=Settings.Summary}\" \r\n                                        Width=\"220\"/>\r\n                    </GridView.Columns>\r\n                </GridView>\r\n            </ListView.View>\r\n\r\n        </ListView>\r\n\r\n        <StackPanel Margin=\"5,0,0,0\"\r\n                    Grid.Row=\"1\"\r\n                    Grid.RowSpan=\"2\"\r\n                    Grid.Column=\"1\">\r\n            <Button Content=\"_Add\"\r\n                    Command=\"{Binding Add}\"\r\n                    Width=\"75\" />\r\n            <Button Content=\"_Remove\"\r\n                    Command=\"{Binding Remove}\"\r\n                    Margin=\"0,3\"\r\n                    Width=\"75\" />\r\n        </StackPanel>\r\n    </Grid>\r\n</UserControl>\r\n"
  },
  {
    "path": "Sentinel/Logs/Gui/ProvidersPage.xaml.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\n{\n    using System.Collections.ObjectModel;\n    using System.Collections.Specialized;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Input;\n    using log4net;\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.NLog;\n    using Sentinel.Providers.Interfaces;\n    using Sentinel.Services;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for ProvidersPage.xaml.\n    /// </summary>\n    public partial class ProvidersPage : IWizardPage, IDataErrorInfo\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(ProvidersPage));\n\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private int selectedProviderIndex = -1;\n\n        private bool isValid;\n\n        public ProvidersPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            Providers = new ObservableCollection<PendingProviderRecord>();\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            Add = new DelegateCommand(AddNewProvider);\n            Remove = new DelegateCommand(RemoveSelectedProvider, e => SelectedProviderIndex != -1);\n\n            Providers.CollectionChanged += ProvidersCollectionChanged;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public ICommand Add { get; private set; }\n\n        public ICommand Remove { get; private set; }\n\n        public ObservableCollection<PendingProviderRecord> Providers { get; private set; }\n\n        public int SelectedProviderIndex\n        {\n            get\n            {\n                return selectedProviderIndex;\n            }\n\n            set\n            {\n                if (selectedProviderIndex != value)\n                {\n                    Log.DebugFormat(\"Selected provider index is {0}\", value);\n                    selectedProviderIndex = value;\n                    OnPropertyChanged(nameof(SelectedProviderIndex));\n                }\n            }\n        }\n\n        public string Title => \"Provider Registration\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public string Description => \"Specify the source-providers for the new logger.\";\n\n        public bool IsValid\n        {\n            get\n            {\n                return isValid;\n            }\n\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent => this;\n\n        /// <summary>\n        /// Gets an error message indicating what is wrong with this object.\n        /// </summary>\n        /// <returns>\n        /// An error message indicating what is wrong with this object. The default is an empty string (\"\").\n        /// </returns>\n        public string Error\n        {\n            get\n            {\n                return this[\"Providers\"];\n            }\n        }\n\n        /// <summary>\n        /// Gets the error message for the property with the given name.\n        /// </summary>\n        /// <returns>\n        /// The error message for the property. The default is an empty string (\"\").\n        /// </returns>\n        /// <param name=\"columnName\">The name of the property whose error message to get.</param>\n        public string this[string columnName]\n        {\n            get\n            {\n                switch (columnName)\n                {\n                    case \"Providers\":\n                        return ValidateProviders();\n                }\n\n                return null;\n            }\n        }\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData != null, \"Expecting an instance to save data into\");\n            Debug.Assert(saveData is NewLoggerSettings, \"Expecting a NewLoggerSettings instance\");\n\n            var settings = saveData as NewLoggerSettings;\n            if (settings != null)\n            {\n                settings.Providers.Clear();\n                foreach (var provider in Providers)\n                {\n                    settings.Providers.Add(provider);\n                }\n            }\n\n            return saveData;\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            PropertyChangedEventHandler handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void AddNewProvider(object obj)\n        {\n            var services = ServiceLocator.Instance;\n            var wizard = services.Get<INewProviderWizard>();\n\n            if (wizard != null)\n            {\n                if (wizard.Display((Window)Parent))\n                {\n                    var info = wizard.Provider;\n                    var settings = wizard.Settings;\n\n                    var rec = new PendingProviderRecord { Info = info, Settings = settings };\n\n                    Providers.Add(rec);\n                }\n            }\n            else\n            {\n                Trace.WriteLine(\"Should have a wizard registered for enrolling new providers.\");\n            }\n        }\n\n        private void RemoveSelectedProvider(object obj)\n        {\n            // TODO: confirmation of deletion.\n            var index = SelectedProviderIndex;\n            if (index != -1 && index < Providers.Count)\n            {\n                Providers.RemoveAt(index);\n            }\n            else\n            {\n                // TODO: error condition.\n            }\n        }\n\n        private string ValidateProviders()\n        {\n            if (Providers == null)\n            {\n                return \"[Internal Error] Providers structure can not be null\";\n            }\n\n            if (Providers.Count == 0)\n            {\n                return \"At least one provider must be specified\";\n            }\n\n            if (Providers.GroupBy(p => p.Settings.Name).Any(g => g.Count() > 1))\n            {\n                return \"Duplicate provider names are not supported, please provide appropriate names.\";\n            }\n\n            var providersWithPorts = Providers.Select(p => p.Settings).OfType<NetworkSettings>().ToList();\n            var providersGroupedByPort = providersWithPorts\n                .GroupBy(p => p.Port).ToList();\n\n            if (providersGroupedByPort.Any(g => g.Count() > 1))\n            {\n                // Duplicate port #\n                var duplicatePort = providersGroupedByPort.First(g => g.Count() > 1).Key;\n                return\n                    $\"Duplicate network ports are not permitted, you have specified port number {duplicatePort} more than once\";\n            }\n\n            var providerManager = ServiceLocator.Instance.Get<IProviderManager>();\n\n            if (providerManager != null\n                && Providers.Select(p => p.Settings.Name).Intersect(providerManager.Instances.Select(p2 => p2.Name)).Any())\n            {\n                var duplicates = Providers.Select(p => p.Settings.Name).Intersect(providerManager.Instances.Select(p2 => p2.Name));\n                return $\"Providers should be uniquely named, there is already one called {duplicates.First()}\";\n            }\n\n            if (providerManager != null\n                && providerManager.Instances.OfType<INetworkProvider>().Any()\n                && providersWithPorts.Any())\n            {\n                // Get the two lists:\n                var instances = providerManager.Instances.OfType<INetworkProvider>().Select(i => i.Port);\n                var newSettings = providersWithPorts.Select(p => p.Port);\n\n                var intersection = instances.Intersect(newSettings).ToList();\n                if (intersection.Any())\n                {\n                    return $\"Network port numbers must be unique, port number {intersection.First()} is already in use.\";\n                }\n            }\n\n            return null;\n        }\n\n        private void PageLoaded(object sender, RoutedEventArgs e)\n        {\n            OnPropertyChanged(nameof(Providers));\n        }\n\n        private void ProvidersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)\n        {\n            OnPropertyChanged(nameof(Providers));\n            IsValid = Error == null;\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Logs/Gui/SetLoggerNamePage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Logs.Gui.SetLoggerNamePage\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\r\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\r\n             mc:Ignorable=\"d\"\r\n             d:DesignHeight=\"300\"\r\n             d:DesignWidth=\"300\"\r\n             Loaded=\"PageLoaded\">\r\n\r\n    <UserControl.Resources>\r\n        <Style x:Key=\"textBoxInError\"\r\n               TargetType=\"{x:Type TextBox}\">\r\n            <Style.Triggers>\r\n                <Trigger Property=\"Validation.HasError\"\r\n                         Value=\"true\">\r\n                    <Setter Property=\"ToolTip\"\r\n                            Value=\"{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors).CurrentItem.ErrorContent}\" />\r\n                </Trigger>\r\n            </Style.Triggers>\r\n        </Style>\r\n    </UserControl.Resources>\r\n\r\n    <Grid Margin=\"4\">\r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition Width=\"Auto\" />\r\n            <ColumnDefinition />\r\n        </Grid.ColumnDefinitions>\r\n\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition Height=\"*\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"*\" />\r\n        </Grid.RowDefinitions>\r\n\r\n        <TextBlock Grid.ColumnSpan=\"2\"\r\n                   Margin=\"5,5,5,15\"\r\n                   VerticalAlignment=\"Bottom\"\r\n                   Text=\"Each logger should be given a unique name, ideally named after something appropriate for your system.\"\r\n                   TextWrapping=\"WrapWithOverflow\" />\r\n        <TextBlock Text=\"Log Name : \"\r\n                   Grid.Row=\"1\"\r\n                   Margin=\"3\"\r\n                   VerticalAlignment=\"Center\" />\r\n        <TextBox Margin=\"3\"\r\n                 Grid.Row=\"1\"\r\n                 Grid.Column=\"1\"\r\n                 Style=\"{StaticResource textBoxInError}\"\r\n                 VerticalAlignment=\"Center\"\r\n                 Text=\"{Binding Path=LogName, \r\n                                ValidatesOnDataErrors=True, \r\n                                Mode=TwoWay, \r\n                                UpdateSourceTrigger=PropertyChanged}\" />\r\n    </Grid>\r\n</UserControl>\r\n"
  },
  {
    "path": "Sentinel/Logs/Gui/SetLoggerNamePage.xaml.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows.Controls;\n\n    using Sentinel.Logs.Interfaces;\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for SetLoggerNamePage.xaml.\n    /// </summary>\n    public partial class SetLoggerNamePage : IWizardPage, IDataErrorInfo\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private readonly ILogManager logManager = Services.ServiceLocator.Instance.Get<ILogManager>();\n\n        private string logName = \"Untitled\";\n\n        private bool isValid;\n\n        public SetLoggerNamePage()\n        {\n            InitializeComponent();\n            DataContext = this;\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n            PropertyChanged += PropertyChangedHandler;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public string LogName\n        {\n            get\n            {\n                return logName;\n            }\n\n            set\n            {\n                if (logName != value)\n                {\n                    logName = value;\n                    OnPropertyChanged(nameof(LogName));\n                }\n            }\n        }\n\n        public string Title => \"Log Name\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public string Description => \"Define a name for the log to be created.\";\n\n        public bool IsValid\n        {\n            get\n            {\n                return isValid;\n            }\n\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent => this;\n\n        /// <summary>\n        /// Gets an error message indicating what is wrong with this object.\n        /// </summary>\n        /// <returns>\n        /// An error message indicating what is wrong with this object. The default is an empty string (\"\").\n        /// </returns>\n        public string Error => this[\"LogName\"];\n\n        /// <summary>\n        /// Gets the error message for the property with the given name.\n        /// </summary>\n        /// <returns>\n        /// The error message for the property.\n        /// </returns>\n        /// <param name=\"columnName\">The name of the property whose error message to get.</param>\n        public string this[string columnName]\n        {\n            get\n            {\n                if (columnName == \"LogName\")\n                {\n                    if (string.IsNullOrEmpty(LogName))\n                    {\n                        return \"Log name may not be blank.\";\n                    }\n\n                    if (logManager != null && logManager.Any(l => l.Name == LogName))\n                    {\n                        return \"A logger with that name already exists\";\n                    }\n                }\n\n                return null;\n            }\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData is NewLoggerSettings, \"Expecting to have a NewLoggerSettings instance\");\n            Debug.Assert(saveData as NewLoggerSettings != null, \"Not expecting a null\");\n\n            var settings = saveData as NewLoggerSettings;\n            if (settings != null)\n            {\n                settings.LogName = LogName;\n            }\n\n            return saveData;\n        }\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"LogName\")\n            {\n                // Validate against standard validation rules.\n                IsValid = this[\"LogName\"] == null;\n            }\n        }\n\n        private void PageLoaded(object sender, System.Windows.RoutedEventArgs e)\n        {\n            OnPropertyChanged(nameof(LogName));\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Logs/Gui/ViewSelectionPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Logs.Gui.ViewSelectionPage\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\r\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\r\n             mc:Ignorable=\"d\"\r\n             d:DesignHeight=\"300\"\r\n             d:DesignWidth=\"500\">\r\n    <Grid Margin=\"3\">\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"*\" />\r\n        </Grid.RowDefinitions>\r\n        \r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition Width=\"106\" />\r\n            <ColumnDefinition Width=\"388*\" />\r\n        </Grid.ColumnDefinitions>\r\n\r\n        <TextBlock Text=\"View Type : \"\r\n                   Margin=\"12,6\"\r\n                   VerticalAlignment=\"Top\"\r\n                   Grid.Row=\"0\" />\r\n\r\n        <StackPanel Grid.Column=\"1\" Margin=\"0,3,3,15\">\r\n            <RadioButton Content=\"Single traditional text based log\"\r\n                         Margin=\"0,3\"\r\n                         IsChecked=\"{Binding SingleView}\" />\r\n            <RadioButton Content=\"Split-screen log with two seperately selectable visualisations\"\r\n                         Margin=\"0,3\"\r\n                         IsEnabled=\"{Binding MultipleViewsSupported}\"\r\n                         IsChecked=\"{Binding MultipleView}\" />\r\n        </StackPanel>\r\n\r\n        <GroupBox Grid.Row=\"1\"\r\n                  Grid.ColumnSpan=\"2\"\r\n                  IsEnabled=\"{Binding MultipleView}\">\r\n            <GroupBox.Header>Split Screen Views</GroupBox.Header>\r\n            <Grid Margin=\"3\">\r\n                <Grid.ColumnDefinitions>\r\n                    <ColumnDefinition Width=\"Auto\" />\r\n                    <ColumnDefinition Width=\"*\" />\r\n                </Grid.ColumnDefinitions>\r\n                <Grid.RowDefinitions>\r\n                    <RowDefinition Height=\"Auto\" />\r\n                    <RowDefinition Height=\"Auto\" />\r\n                    <RowDefinition Height=\"*\" />\r\n                </Grid.RowDefinitions>\r\n\r\n                <TextBlock Text=\"Primary View : \"\r\n                           Margin=\"3\"\r\n                           VerticalAlignment=\"Center\"\r\n                           Grid.Row=\"0\" />\r\n                <ComboBox Margin=\"3\"\r\n                          Grid.Row=\"0\"\r\n                          Grid.Column=\"1\"\r\n                          VerticalAlignment=\"Center\"\r\n                          DisplayMemberPath=\"Name\"\r\n                          SelectedIndex=\"{Binding PrimaryIndex}\"\r\n                          ItemsSource=\"{Binding RegisteredViews}\" />\r\n                <TextBlock Text=\"Secondary View : \"\r\n                           Margin=\"3\"\r\n                           Grid.Row=\"1\" />\r\n                <ComboBox Margin=\"3\"\r\n                          Grid.Row=\"1\"\r\n                          Grid.Column=\"1\"\r\n                          VerticalAlignment=\"Center\"\r\n                          DisplayMemberPath=\"Name\"\r\n                          SelectedIndex=\"{Binding SecondaryIndex}\"\r\n                          ItemsSource=\"{Binding RegisteredViews}\" />\r\n                <TextBlock Text=\"Orientation : \"\r\n                           Margin=\"3\"\r\n                           VerticalAlignment=\"Top\"\r\n                           Grid.Row=\"2\" />\r\n\r\n                <StackPanel Grid.Row=\"2\"\r\n                            Grid.Column=\"1\">\r\n                    <RadioButton Content=\"Horizontal\"\r\n                                 IsChecked=\"{Binding Horizontal}\"\r\n                                 Margin=\"3\" />\r\n                    <RadioButton Content=\"Vertical\"\r\n                                 IsChecked=\"{Binding Vertical}\"\r\n                                 Margin=\"3\" />\r\n                </StackPanel>\r\n            </Grid>\r\n        </GroupBox>\r\n    </Grid>\r\n</UserControl>"
  },
  {
    "path": "Sentinel/Logs/Gui/ViewSelectionPage.xaml.cs",
    "content": "﻿namespace Sentinel.Logs.Gui\n{\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows.Controls;\n\n    using Sentinel.Services;\n    using Sentinel.Views.Interfaces;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for ViewSelectionPage.xaml.\n    /// </summary>\n    public partial class ViewSelectionPage : IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private bool horizontal;\n\n        private bool multipleView;\n\n        private int primaryIndex;\n\n        private int secondaryIndex;\n\n        private bool singleView = true;\n\n        private bool vertical = true;\n\n        private bool multipleViewsSupported = false;\n\n        private IEnumerable<IViewInformation> registeredViews;\n\n        public ViewSelectionPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            IViewManager vm = ServiceLocator.Instance.Get<IViewManager>();\n            if (vm != null)\n            {\n                registeredViews = new List<IViewInformation>(vm.Registered);\n\n#if DISABLE_MULTIPLE_VIEWS\n                MultipleViewsSupported = registeredViews.Count() > 1;\n#endif\n                SecondaryIndex = registeredViews.Count() > 1 ? 1 : PrimaryIndex;\n            }\n\n            PropertyChanged += PropertyChangedHandler;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public bool Horizontal\n        {\n            get\n            {\n                return horizontal;\n            }\n\n            set\n            {\n                if (horizontal == value)\n                {\n                    return;\n                }\n\n                horizontal = value;\n                OnPropertyChanged(nameof(Horizontal));\n            }\n        }\n\n        public bool Vertical\n        {\n            get\n            {\n                return vertical;\n            }\n\n            set\n            {\n                if (vertical != value)\n                {\n                    vertical = value;\n                    OnPropertyChanged(nameof(Vertical));\n                }\n            }\n        }\n\n        public bool MultipleViewsSupported\n        {\n            get\n            {\n                return multipleViewsSupported;\n            }\n\n            private set\n            {\n                if (multipleViewsSupported != value)\n                {\n                    multipleViewsSupported = value;\n                    OnPropertyChanged(nameof(MultipleViewsSupported));\n                }\n            }\n        }\n\n        public bool MultipleView\n        {\n            get\n            {\n                return multipleView;\n            }\n\n            set\n            {\n                if (multipleView != value)\n                {\n                    multipleView = value;\n                    OnPropertyChanged(nameof(MultipleView));\n                }\n            }\n        }\n\n        public bool SingleView\n        {\n            get\n            {\n                return singleView;\n            }\n\n            set\n            {\n                if (singleView != value)\n                {\n                    singleView = value;\n                    OnPropertyChanged(nameof(SingleView));\n                }\n            }\n        }\n\n        public IEnumerable<IViewInformation> RegisteredViews\n        {\n            get\n            {\n                return registeredViews;\n            }\n\n            private set\n            {\n                if (!Equals(registeredViews, value))\n                {\n                    registeredViews = value;\n                    OnPropertyChanged(nameof(RegisteredViews));\n                }\n            }\n        }\n\n        public int PrimaryIndex\n        {\n            get\n            {\n                return primaryIndex;\n            }\n\n            set\n            {\n                if (primaryIndex != value)\n                {\n                    primaryIndex = value;\n                    OnPropertyChanged(nameof(PrimaryIndex));\n                }\n            }\n        }\n\n        public int SecondaryIndex\n        {\n            get\n            {\n                return secondaryIndex;\n            }\n\n            set\n            {\n                if (secondaryIndex != value)\n                {\n                    secondaryIndex = value;\n                    OnPropertyChanged(nameof(SecondaryIndex));\n                }\n            }\n        }\n\n        public string Title => \"Visualising the Log\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public string Description => \"Select the desired views to visualise the logger and its providers.\";\n\n        public bool IsValid => true;\n\n        public Control PageContent => this;\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData != null, \"Expecting a non-null instance of a class to save settings into\");\n            Debug.Assert(saveData is NewLoggerSettings, \"Expecting save data structure to be a NewLoggerSettings\");\n\n            var settings = saveData as NewLoggerSettings;\n            if (settings != null)\n            {\n                settings.Views.Clear();\n                settings.Views.Add(registeredViews.ElementAt(PrimaryIndex).Identifier);\n                if (MultipleView)\n                {\n                    settings.Views.Add(registeredViews.ElementAt(SecondaryIndex).Identifier);\n                    settings.IsVertical = Vertical;\n                }\n            }\n\n            return saveData;\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            switch (e.PropertyName)\n            {\n                case \"Horizontal\":\n                    Vertical = !Horizontal;\n                    break;\n                case \"Vertical\":\n                    Horizontal = !Vertical;\n                    break;\n                case \"SingleView\":\n                    MultipleView = !SingleView;\n                    break;\n                case \"MultipleView\":\n                    SingleView = !MultipleView;\n                    break;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Logs/Interfaces/ILogFileExporter.cs",
    "content": "﻿namespace Sentinel.Logs.Interfaces\n{\n    using Sentinel.Views.Interfaces;\n\n    public interface ILogFileExporter\n    {\n        void SaveLogViewerToFile(IWindowFrame windowFrame, string filePath);\n    }\n}\n"
  },
  {
    "path": "Sentinel/Logs/Interfaces/ILogManager.cs",
    "content": "namespace Sentinel.Logs.Interfaces\n{\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n\n    public interface ILogManager : IEnumerable<ILogger>\n    {\n        ILogger Add(string logName);\n\n        ILogger Get(string name);\n\n        void Remove(string name);\n    }\n}"
  },
  {
    "path": "Sentinel/Logs/Log.cs",
    "content": "namespace Sentinel.Logs\n{\n    using System;\n    using System.Collections.Generic;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Services;\n\n    using WpfExtras;\n\n    public class Log : ViewModelBase, ILogger\n    {\n        private readonly IClassifyingService<IClassifier> classifier;\n\n        private readonly IUserPreferences preferences;\n\n        private readonly List<ILogEntry> entries = new List<ILogEntry>();\n\n        private readonly List<ILogEntry> newEntries = new List<ILogEntry>();\n\n        private bool enabled = true;\n\n        private string name;\n\n        public Log()\n        {\n            Entries = entries;\n            NewEntries = newEntries;\n\n            classifier = ServiceLocator.Instance.Get<IClassifyingService<IClassifier>>();\n            preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n\n            // Observe the NewEntries to maintain a full history.\n            PropertyChanged += OnPropertyChanged;\n        }\n\n        public IEnumerable<ILogEntry> Entries { get; private set; }\n\n        public bool Enabled\n        {\n            get => enabled;\n\n            set\n            {\n                if (enabled != value)\n                {\n                    enabled = value;\n                    OnPropertyChanged(nameof(Enabled));\n                }\n            }\n        }\n\n        public string Name\n        {\n            get => name;\n\n            set\n            {\n                if (value != name)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(Name));\n                }\n            }\n        }\n\n        public IEnumerable<ILogEntry> NewEntries { get; private set; }\n\n        public void Clear()\n        {\n            lock (entries)\n            {\n                entries.Clear();\n            }\n\n            lock (newEntries)\n            {\n                newEntries.Clear();\n            }\n\n            OnPropertyChanged(nameof(Entries));\n            OnPropertyChanged(nameof(NewEntries));\n            GC.Collect();\n        }\n\n        public void AddBatch(Queue<ILogEntry> incomingEntries)\n        {\n            if (!enabled || incomingEntries.Count <= 0)\n            {\n                return;\n            }\n\n            var processed = new Queue<ILogEntry>();\n            while (incomingEntries.Count > 0)\n            {\n                if (classifier != null)\n                {\n                    var entry = classifier.Classify(incomingEntries.Dequeue());\n                    processed.Enqueue(entry);\n                }\n            }\n\n            lock (newEntries)\n            {\n                newEntries.Clear();\n                newEntries.AddRange(processed);\n            }\n\n            OnPropertyChanged(nameof(NewEntries));\n        }\n\n        public void LimitMessageCount(int maximumMessages)\n        {\n            lock (entries)\n            {\n                var messages = entries.Count;\n                var excessMessages = messages - maximumMessages;\n\n                if (excessMessages > 0)\n                {\n                    entries.RemoveRange(0, excessMessages);\n                }\n            }\n        }\n\n        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"NewEntries\")\n            {\n                lock (newEntries)\n                {\n                    lock (entries)\n                    {\n                        var entriesToAppend = newEntries.ToList();\n\n                        // Look for any special command\n                        if (preferences != null && preferences.EnableClearCommand)\n                        {\n                            if (entriesToAppend.Any(entry => entry.Description == preferences.ClearCommandMatchText))\n                            {\n                                Trace.WriteLine(\"!!!!!!!!!!!! CLEAR COMMAND FOUND !!!!!!!!!!\");\n                            }\n\n                            var indexOfClear = entriesToAppend.FindLastIndex(\n                                entry => entry.Description == preferences.ClearCommandMatchText);\n                            if (indexOfClear != -1)\n                            {\n                                Trace.WriteLine(\n                                    $\"Clear command found (message {indexOfClear} of {newEntries.Count} incoming messages)\");\n                                entriesToAppend = newEntries.Skip(indexOfClear + 1).ToList();\n                                Trace.WriteLine($\"Message buffer of {entries.Count} messages being cleared\");\n\n                                entries.Clear();\n                                OnPropertyChanged(nameof(Entries));\n\n                                Debug.Assert(entries.Count == 0, \"should have cleared entries\");\n\n                                newEntries.Clear();\n                                newEntries.AddRange(entriesToAppend);\n                                OnPropertyChanged(nameof(NewEntries));\n\n                                return;\n                            }\n                        }\n\n                        entries.AddRange(entriesToAppend);\n                    }\n\n                    OnPropertyChanged(nameof(Entries));\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Logs/LogFileExporter.cs",
    "content": "﻿namespace Sentinel.Logs\n{\n    using System.IO;\n    using System.Text;\n\n    using Sentinel.Logs.Interfaces;\n    using Sentinel.Views.Interfaces;\n\n    public class LogFileExporter : ILogFileExporter\n    {\n        public LogFileExporter()\n        {\n        }\n\n        public void SaveLogViewerToFile(IWindowFrame windowFrame, string filePath)\n        {\n            // Check if file exists; delete it\n            if (File.Exists(filePath))\n            {\n                File.Delete(filePath);\n            }\n\n            using (var fs = File.Create(filePath))\n            {\n                var messages = windowFrame.PrimaryView.Messages;\n                lock (messages)\n                {\n                    foreach (var msg in messages)\n                    {\n                        AddText(\n                            fs,\n                            $\"{msg.DateTime.ToString(\"yyyy-MM-dd HH:mm:ss.ffff\")}|{msg.Type}|{msg.System}|{msg.Description}\\r\\n\");\n                    }\n                }\n            }\n        }\n\n        private static void AddText(FileStream fs, string value)\n        {\n            byte[] info = new UTF8Encoding(true).GetBytes(value);\n            fs.Write(info, 0, info.Length);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Logs/LogManager.cs",
    "content": "﻿namespace Sentinel.Logs\n{\n    using System;\n    using System.Collections;\n    using System.Collections.Generic;\n    using System.Diagnostics;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Logs.Interfaces;\n\n    using WpfExtras;\n\n    public class LogManager : ViewModelBase, ILogManager\n    {\n        private readonly Dictionary<string, ILogger> loggers = new Dictionary<string, ILogger>();\n\n        public ILogger Add(string logName)\n        {\n            logName.ThrowIfNullOrWhiteSpace(nameof(logName));\n\n            Debug.Assert(!loggers.ContainsKey(logName), \"Log name has already been used.\");\n            if (loggers.ContainsKey(logName))\n            {\n                throw new ArgumentException(\"LogManager does not support duplicate log names.\", nameof(logName));\n            }\n\n            var log = new Log { Name = logName };\n\n            loggers[logName] = log;\n            return log;\n        }\n\n        public ILogger Get(string name)\n        {\n            return loggers[name];\n        }\n\n        public void Remove(string name)\n        {\n            if (loggers.ContainsKey(name))\n            {\n                loggers.Remove(name);\n            }\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through the collection.\n        /// </summary>\n        /// <returns>\n        /// A <see cref=\"T:System.Collections.Generic.IEnumerator`1\"/>\n        /// that can be used to iterate through the collection.\n        /// </returns>\n        /// <filterpriority>1.</filterpriority>\n        public IEnumerator<ILogger> GetEnumerator()\n        {\n            return loggers.Values.GetEnumerator();\n        }\n\n        /// <summary>\n        /// Returns an enumerator that iterates through a collection.\n        /// </summary>\n        /// <returns>\n        /// An <see cref=\"T:System.Collections.IEnumerator\"/>\n        /// object that can be used to iterate through the collection.\n        /// </returns>\n        /// <filterpriority>2.</filterpriority>\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            return GetEnumerator();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/ConfigurationPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.MSBuild.ConfigurationPage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n             mc:Ignorable=\"d\"\n             d:DesignHeight=\"226\"\n             d:DesignWidth=\"294\"\n             SnapsToDevicePixels=\"True\"\n             Loaded=\"OnLoaded\">\n\n    <UserControl.Resources>\n        <converters:BooleanInvertingValueConverter x:Key=\"InvertConverter\" />\n        <converters:VisibilityToHiddenConverter x:Key=\"visToHiddenConverter\" />\n    </UserControl.Resources>\n    \n    <Grid Margin=\"3\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"40\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n        <StackPanel Grid.Row=\"0\"\n                    Grid.Column=\"1\"\n                    IsEnabled=\"False\" />\n\n        <TextBlock Text=\"Port : \"\n                   Margin=\"3\"\n                   Grid.Row=\"2\"\n                   VerticalAlignment=\"Center\" />\n        <TextBox x:Name=\"portText\"\n                 Margin=\"3\"\n                 Text=\"{Binding Port, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}\"\n                 Grid.Row=\"2\"\n                 Grid.Column=\"1\"\n                 VerticalAlignment=\"Center\" />\n        <TextBlock Margin=\"3\"\n                   Grid.Row=\"3\"\n                   Grid.Column=\"1\"\n                   VerticalAlignment=\"Top\"\n                   TextWrapping=\"WrapWithOverflow\"><Run Text=\"Please note that depending upon your firewall settings, you may be prompted to confirm the opening of a network port.\" /><LineBreak /><Run Text=\"This normally only occurs the first time.\" /></TextBlock>\n\n        <TextBlock\n            Text=\"In Development - Hardcoded values\"\n            FontSize=\"30\"\n            Height=\"87\"\n            Foreground=\"Red\"\n            Grid.ColumnSpan=\"2\"\n            Grid.Row=\"1\"\n            VerticalAlignment=\"Center\"\n            TextAlignment=\"Center\"\n            TextWrapping=\"WrapWithOverflow\"\n            RenderTransformOrigin=\"0.5,0.5\"\n            Grid.RowSpan=\"3\"\n            HorizontalAlignment=\"Center\">\n            <TextBlock.Effect>\n                <DropShadowEffect />\n            </TextBlock.Effect>\n            <TextBlock.RenderTransform>\n                <TransformGroup>\n                    <ScaleTransform />\n                    <SkewTransform />\n                    <RotateTransform\n                        Angle=\"-30\" />\n                    <TranslateTransform />\n                </TransformGroup>\n            </TextBlock.RenderTransform>\n        </TextBlock>\n\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/MSBuild/ConfigurationPage.xaml.cs",
    "content": "﻿namespace Sentinel.MSBuild\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Windows.Controls;\n\n    using Sentinel.Interfaces.Providers;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for ConfigurationPage.xaml.\n    /// </summary>\n    public partial class ConfigurationPage : IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private bool isValid;\n\n        private int port;\n\n        public ConfigurationPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            // Register to self so that we can handler user interactions.\n            PropertyChanged += SelectProviderPagePropertyChanged;\n\n            Port = 9998;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public int Port\n        {\n            get\n            {\n                return port;\n            }\n\n            set\n            {\n                if (port != value)\n                {\n                    port = value;\n                    OnPropertyChanged(nameof(Port));\n                }\n            }\n        }\n\n        public string Title => \"Configure Provider\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public string Description => \"Network settings to be used by new provider\";\n\n        public bool IsValid\n        {\n            get => isValid;\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent => this;\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(saveData != null, \"Expecting the save-data component to have details from the previous pages.\");\n            Debug.Assert(\n                saveData is IProviderSettings, \"Expecting the save-data component to be of an IProviderSettings type.\");\n\n            var previousInfo = (IProviderSettings)saveData;\n            return new MsBuildListenerSettings(previousInfo);\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void OnLoaded(object sender, System.Windows.RoutedEventArgs e)\n        {\n        }\n\n        private void SelectProviderPagePropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"Port\")\n            {\n                var state = port > 2000;\n                Trace.WriteLine($\"Setting PageValidates to {state}\");\n                IsValid = state;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/IMSBuildListenerSettings.cs",
    "content": "namespace Sentinel.MSBuild\n{\n    using Sentinel.Interfaces.Providers;\n\n    public interface IMsBuildListenerSettings : IProviderSettings\n    {\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/LogEntry.cs",
    "content": "namespace Sentinel.MSBuild\n{\n    using System;\n    using System.Collections.Generic;\n\n    using Newtonsoft.Json.Linq;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    internal class LogEntry : ILogEntry\n    {\n        public LogEntry()\n        {\n        }\n\n        public LogEntry(string msbuildEventType, JObject content)\n        {\n            if (string.IsNullOrWhiteSpace(msbuildEventType))\n            {\n                throw new ArgumentNullException(nameof(msbuildEventType));\n            }\n\n            content.ThrowIfNull(nameof(content));\n\n            switch (msbuildEventType)\n            {\n                case \"ErrorRaised\":\n                    Type = \"ERROR\";\n                    break;\n                case \"WarningRaised\":\n                    Type = \"WARN\";\n                    break;\n                default:\n                    Type = \"INFO\";\n                    break;\n            }\n\n            Description = (string)content[\"Message\"];\n            DateTime = (DateTime)content[\"Timestamp\"];\n            Thread = ((int)content[\"ThreadId\"]).ToString();\n            Source = (string)content[\"SenderName\"];\n            System = msbuildEventType;\n\n            if (Description.ToUpper().Contains(\"EXCEPTION\"))\n            {\n                MetaData.Add(\"Exception\", true);\n            }\n\n            MetaData = new Dictionary<string, object> { { \"Original\", content } };\n        }\n\n        /// <summary>\n        /// Classification for the log entry.  Can be free-text but will typically\n        /// contain values like \"DEBUG\" or \"ERROR\".\n        /// </summary>\n        public string Type { get; set; }\n\n        /// <summary>\n        /// Date/Time for the original log entry.\n        /// </summary>\n        public DateTime DateTime { get; set; }\n\n        /// <summary>\n        /// The main body of the log entry.\n        /// </summary>\n        public string Description { get; set; }\n\n        /// <summary>\n        /// Source of the log entry, e.g. where it came from.\n        /// </summary>\n        public string Source { get; set; }\n\n        /// <summary>\n        /// The system (e.g. machine) where this message came from.\n        /// </summary>\n        public string System { get; set; }\n\n        /// <summary>\n        /// Thread identifier for the source of the message.\n        /// </summary>\n        public string Thread { get; set; }\n\n        /// <summary>\n        /// Dictionary of any meta-data that doesn't fit into the above values.\n        /// </summary>\n        public Dictionary<string, object> MetaData { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/MSBuildListenerSettings.cs",
    "content": "namespace Sentinel.MSBuild\n{\n    using Sentinel.Interfaces.Providers;\n\n    public class MsBuildListenerSettings : IMsBuildListenerSettings\n    {\n        public MsBuildListenerSettings(IProviderSettings providerSettings)\n        {\n            this.ProviderSettings = providerSettings;\n        }\n\n        public string Name => ProviderSettings.Name;\n\n        public string Summary => ProviderSettings.Summary;\n\n        public IProviderInfo Info => ProviderSettings.Info;\n\n        private IProviderSettings ProviderSettings { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/MSBuildProvider.cs",
    "content": "namespace Sentinel.MSBuild\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Net;\n    using System.Net.Sockets;\n    using System.Text;\n    using System.Threading;\n    using System.Threading.Tasks;\n    using log4net;\n    using Newtonsoft.Json.Linq;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Interfaces.Providers;\n\n    public class MsBuildProvider : INetworkProvider\n    {\n        public static readonly IProviderRegistrationRecord ProviderRegistrationRecord =\n            new ProviderRegistrationInformation(new ProviderInfo());\n\n        private const int PumpFrequency = 100;\n\n        private static readonly ILog Log = LogManager.GetLogger(typeof(MsBuildProvider));\n\n        private readonly Queue<string> pendingQueue = new Queue<string>();\n\n        private CancellationTokenSource cancellationTokenSource;\n\n        private Task listenerTask;\n\n        public MsBuildProvider(IProviderSettings settings)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            Settings = settings as IMsBuildListenerSettings;\n            Settings.ThrowIfNull(nameof(Settings));\n\n            ProviderSettings = settings;\n        }\n\n        public IProviderInfo Information { get; private set; }\n\n        public IProviderSettings ProviderSettings { get; private set; }\n\n        public ILogger Logger { get; set; }\n\n        public string Name { get; set; }\n\n        public bool IsActive => listenerTask != null && listenerTask.Status == TaskStatus.Running;\n\n        public int Port { get; private set; }\n\n        protected IMsBuildListenerSettings Settings { get; set; }\n\n        public void Start()\n        {\n            Log.Debug(\"Start requested\");\n\n            if (listenerTask == null || listenerTask.IsCompleted)\n            {\n                cancellationTokenSource = new CancellationTokenSource();\n                var token = cancellationTokenSource.Token;\n\n                listenerTask = Task.Factory.StartNew(SocketListener, token);\n                Task.Factory.StartNew(MessagePump, token);\n            }\n            else\n            {\n                Log.Warn(\"UDP listener task is already active and can not be started again.\");\n            }\n        }\n\n        public void Pause()\n        {\n            Log.Debug(\"Pause requested\");\n            if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)\n            {\n                Log.Debug(\"Cancellation token triggered\");\n                cancellationTokenSource.Cancel();\n            }\n        }\n\n        public void Close()\n        {\n            Log.Debug(\"Close requested\");\n            if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)\n            {\n                Log.Debug(\"Cancellation token triggered\");\n                cancellationTokenSource.Cancel();\n            }\n        }\n\n        private void SocketListener()\n        {\n            Log.Debug(\"SocketListener started\");\n\n            while (!cancellationTokenSource.IsCancellationRequested)\n            {\n                var endPoint = new IPEndPoint(IPAddress.Any, 9123);\n\n                using (var listener = new UdpClient(endPoint))\n                {\n                    var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);\n                    listener.Client.ReceiveTimeout = 1000;\n\n                    while (!cancellationTokenSource.IsCancellationRequested)\n                    {\n                        try\n                        {\n                            var bytes = listener.Receive(ref remoteEndPoint);\n\n                            if (Log.IsDebugEnabled)\n                                Log.DebugFormat(\"Received {0} bytes from {1}\", bytes.Length, remoteEndPoint.Address);\n\n                            var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length);\n                            lock (pendingQueue)\n                            {\n                                pendingQueue.Enqueue(message);\n                            }\n                        }\n                        catch (SocketException socketException)\n                        {\n                            if (socketException.SocketErrorCode != SocketError.TimedOut)\n                            {\n                                Log.Error(\"SocketException\", socketException);\n                                Log.Debug($\"SocketException.SocketErrorCode = {socketException.SocketErrorCode}\");\n\n                                // Break out of the 'using socket' loop and try to establish a new socket.\n                                break;\n                            }\n                        }\n                        catch (Exception e)\n                        {\n                            Log.Error(\"UdpClient Exception\", e);\n                        }\n                    }\n                }\n            }\n\n            Log.Debug(\"SocketListener completed\");\n        }\n\n        private void MessagePump()\n        {\n            Log.Debug(\"MessagePump started\");\n\n            var processedQueue = new Queue<ILogEntry>();\n\n            while (!cancellationTokenSource.IsCancellationRequested)\n            {\n                Thread.Sleep(PumpFrequency);\n\n                try\n                {\n                    if (Logger != null)\n                    {\n                        lock (pendingQueue)\n                        {\n                            while (pendingQueue.Count > 0)\n                            {\n                                var message = pendingQueue.Dequeue();\n\n                                // TODO: validate\n                                if (IsValidMessage(message))\n                                {\n                                    var deserializeMessage = DeserializeMessage(message);\n\n                                    if (deserializeMessage != null)\n                                    {\n                                        processedQueue.Enqueue(deserializeMessage);\n                                    }\n                                }\n                            }\n                        }\n\n                        if (processedQueue.Any())\n                        {\n                            Logger.AddBatch(processedQueue);\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    Log.Error(\"MessagePump Exception\", e);\n                }\n                finally\n                {\n                    processedQueue.Clear();\n                }\n            }\n\n            Log.Debug(\"MessagePump completed\");\n        }\n\n        private ILogEntry DeserializeMessage(string message)\n        {\n            try\n            {\n                var json = JToken.Parse(message);\n\n                var jsonObject = json as JObject;\n\n                if (jsonObject != null && jsonObject.Children().Count() == 1)\n                {\n                    var property = jsonObject.Children().First() as JProperty;\n\n                    if (property == null)\n                    {\n                        Log.Error(\"First item in JObject should be a property\");\n                    }\n                    else\n                    {\n                        var msbuildEventType = property.Name;\n                        var content = property.Value as JObject;\n\n                        if (string.IsNullOrWhiteSpace(msbuildEventType) || content == null)\n                        {\n                            Log.ErrorFormat(\n                                \"Expected payload to consist of a property corresponding to the MSBuild event type name, \"\n                                + \"and a value which is the serialized object corresponding to the type.\");\n                        }\n                        else\n                        {\n                            return new LogEntry(msbuildEventType, content);\n                        }\n                    }\n                }\n            }\n            catch (Exception e)\n            {\n                Log.Error(\"Deserialization exception trying to turn the JSON content into a LogMessage\", e);\n            }\n\n            return null;\n        }\n\n        private bool IsValidMessage(string message)\n        {\n            // TODO: validation logic required.\n            return true;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/ProviderInfo.cs",
    "content": "namespace Sentinel.MSBuild\n{\n    using System;\n\n    using Sentinel.Interfaces.Providers;\n\n    public class ProviderInfo : IProviderInfo\n    {\n        public Guid Identifier => new Guid(\"87270254-9EB6-4AF3-9008-0147DE849168\");\n\n        public string Name => \"MSBuild UDP Listener\";\n\n        public string Description => \"Listens for JSON serialized MSBuild logging events passed via UDP\";\n    }\n}"
  },
  {
    "path": "Sentinel/MSBuild/ProviderRegistrationInformation.cs",
    "content": "namespace Sentinel.MSBuild\n{\n    using System;\n\n    using Sentinel.Interfaces.Providers;\n\n    public class ProviderRegistrationInformation : IProviderRegistrationRecord\n    {\n        public ProviderRegistrationInformation(IProviderInfo providerInfo)\n        {\n            Info = providerInfo;\n        }\n\n        public Guid Identifier => Info.Identifier;\n\n        public IProviderInfo Info { get; private set; }\n\n        public Type Settings => typeof(ConfigurationPage);\n\n        public Type Implementer => typeof(MsBuildProvider);\n    }\n}"
  },
  {
    "path": "Sentinel/MainApplication.xaml",
    "content": "﻿<Application x:Class=\"Sentinel.MainApplication\"\r\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n    StartupUri=\"Controls/MainWindow.xaml\">\r\n    <Application.Resources>\r\n        <Canvas x:Key=\"UpArrow\"\r\n                x:Shared=\"false\"\r\n                Width=\"16\"\r\n                Height=\"16\">\r\n            <Path Width=\"16\"\r\n                  Height=\"16\"\r\n                  Canvas.Left=\"0\"\r\n                  Canvas.Top=\"0\"\r\n                  Data=\"F1 M 32 0 0 32 20 32 20 64 44 64 44 32 64 32 Z\"\r\n                  Stroke=\"#FF000000\"\r\n                  Stretch=\"Fill\">\r\n                <Path.Fill>\r\n                    <LinearGradientBrush StartPoint=\"0.5,0\"\r\n                                         EndPoint=\"0.5,1\">\r\n                        <LinearGradientBrush.GradientStops>\r\n                            <GradientStop Color=\"#FFF9FFEB\"\r\n                                          Offset=\"0\" />\r\n                            <GradientStop Color=\"#FFDFF0C4\"\r\n                                          Offset=\"0.156559\" />\r\n                            <GradientStop Color=\"#FFC6E19D\"\r\n                                          Offset=\"0.306977\" />\r\n                            <GradientStop Color=\"#FFB2D465\"\r\n                                          Offset=\"0.427907\" />\r\n                            <GradientStop Color=\"#FF9FC82D\"\r\n                                          Offset=\"0.576744\" />\r\n                            <GradientStop Color=\"#FF6E901F\"\r\n                                          Offset=\"0.744186\" />\r\n                            <GradientStop Color=\"#FF3E5912\"\r\n                                          Offset=\"0.939535\" />\r\n                        </LinearGradientBrush.GradientStops>\r\n                    </LinearGradientBrush>\r\n                </Path.Fill>\r\n            </Path>\r\n        </Canvas>\r\n\r\n        <Canvas x:Key=\"DownArrow\"\r\n                x:Shared=\"false\"\r\n                Width=\"16\"\r\n                Height=\"16\">\r\n            <Path Width=\"16\"\r\n                  Height=\"16\"\r\n                  Canvas.Left=\"0\"\r\n                  Canvas.Top=\"0\"\r\n                  Data=\"F1 M 32 64 0 32 20 32 20 0 44 0 44 32 64 32 Z\"\r\n                  Stroke=\"#FF000000\"\r\n                  Stretch=\"Fill\">\r\n                <Path.Fill>\r\n                    <LinearGradientBrush StartPoint=\"0.5,0\"\r\n                                         EndPoint=\"0.5,1\">\r\n                        <LinearGradientBrush.GradientStops>\r\n                            <GradientStop Color=\"#FFF9FFEB\"\r\n                                          Offset=\"0\" />\r\n                            <GradientStop Color=\"#FFDFF0C4\"\r\n                                          Offset=\"0.156559\" />\r\n                            <GradientStop Color=\"#FFC6E19D\"\r\n                                          Offset=\"0.306977\" />\r\n                            <GradientStop Color=\"#FFB2D465\"\r\n                                          Offset=\"0.427907\" />\r\n                            <GradientStop Color=\"#FF9FC82D\"\r\n                                          Offset=\"0.576744\" />\r\n                            <GradientStop Color=\"#FF6E901F\"\r\n                                          Offset=\"0.744186\" />\r\n                            <GradientStop Color=\"#FF3E5912\"\r\n                                          Offset=\"0.939535\" />\r\n                        </LinearGradientBrush.GradientStops>\r\n                    </LinearGradientBrush>\r\n                </Path.Fill>\r\n            </Path>\r\n        </Canvas>\r\n\r\n    </Application.Resources>\r\n</Application>\r\n"
  },
  {
    "path": "Sentinel/MainApplication.xaml.cs",
    "content": "﻿namespace Sentinel\n{\n    using System.Windows;\n\n    using Sentinel.Properties;\n    using Sentinel.Services;\n    using Sentinel.Services.Interfaces;\n\n    /// <summary>\n    /// Interaction logic for MainApplication.xaml.\n    /// </summary>\n    public partial class MainApplication : Application\n    {\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"MainApplication\"/> class.\n        /// </summary>\n        public MainApplication()\n        {\n            ////AppDomain.CurrentDomain.FirstChanceException += FirstChanceExceptionHandler;\n            Settings.Default.Upgrade();\n\n            ServiceLocator locator = ServiceLocator.Instance;\n            locator.ReportErrors = true;\n            locator.Register<ISessionManager>(new SessionManager());\n\n            // Request that the application close on main window close.\n            ShutdownMode = ShutdownMode.OnMainWindowClose;\n        }\n\n        ////private void FirstChanceExceptionHandler(object sender, FirstChanceExceptionEventArgs e)\n        ////{\n        ////    if (e.Exception is SocketException)\n        ////    {\n        ////        return;\n        ////    }\n\n        ////    var source = e.Exception.Source?.ToLower();\n        ////    if (source == \"mscorlib\" || source == \"squirrel\")\n        ////    {\n        ////        return;\n        ////    }\n\n        ////    var sb = new StringBuilder();\n        ////    sb.AppendLine($\"Sender: {sender} FirstChanceException raised in {AppDomain.CurrentDomain.FriendlyName}\");\n        ////    sb.AppendLine($\"Message - {e.Exception.Message}\");\n        ////    sb.AppendLine($\"InnerException -- {e.Exception?.InnerException?.Message ?? string.Empty}\");\n        ////    sb.AppendLine($\"TargetSite - {e.Exception?.TargetSite?.Name ?? string.Empty}\");\n        ////    sb.AppendLine($\"StackTrace - {e.Exception?.StackTrace ?? string.Empty}\");\n        ////    sb.AppendLine($\"HelpLink -- {e.Exception?.HelpLink ?? string.Empty} \");\n\n        ////    MessageBox.Show(\n        ////        sb.ToString(),\n        ////        \"Error \" + e.Exception.GetType(),\n        ////        MessageBoxButton.OK,\n        ////        MessageBoxImage.Error,\n        ////        MessageBoxResult.OK);\n        ////}\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/INLogAppenderSettings.cs",
    "content": "namespace Sentinel.NLog\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces.Providers;\n\n    public interface INLogAppenderSettings : IProviderSettings\n    {\n        [DataMember]\n        NetworkProtocol Protocol { get; set; }\n\n        [DataMember]\n        int Port { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/LogEntry.cs",
    "content": "namespace Sentinel.NLog\n{\n    using System;\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n\n    public class LogEntry : ILogEntry\n    {\n        public LogEntry()\n        {\n            Type = \"DEBUG\";\n            DateTime = DateTime.Now;\n            Description = \"Fake Message\";\n            Source = \"Fake source\";\n            System = \"System\";\n            Thread = \"123\";\n        }\n\n        /// <summary>\n        /// Classification for the log entry.  Can be free-text but will typically\n        /// contain values like \"DEBUG\" or \"ERROR\".\n        /// </summary>\n        public string Type { get; set; }\n\n        /// <summary>\n        /// Date/Time for the original log entry.\n        /// </summary>\n        public DateTime DateTime { get; set; }\n\n        /// <summary>\n        /// The main body of the log entry.\n        /// </summary>\n        public string Description { get; set; }\n\n        /// <summary>\n        /// Source of the log entry, e.g. where it came from.\n        /// </summary>\n        public string Source { get; set; }\n\n        /// <summary>\n        /// The system (e.g. machine) where this message came from.\n        /// </summary>\n        public string System { get; set; }\n\n        /// <summary>\n        /// Thread identifier for the source of the message.\n        /// </summary>\n        public string Thread { get; set; }\n\n        /// <summary>\n        /// Dictionary of any meta-data that doesn't fit into the above values.\n        /// </summary>\n        public Dictionary<string, object> MetaData { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/NLogViewerProvider.cs",
    "content": "﻿namespace Sentinel.NLog\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Net;\n    using System.Net.Sockets;\n    using System.Text;\n    using System.Threading;\n    using System.Threading.Tasks;\n    using System.Xml.Linq;\n    using log4net;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Interfaces.Providers;\n\n    public class NLogViewerProvider : INetworkProvider\n    {\n        public static readonly IProviderRegistrationRecord ProviderRegistrationInformation\n            = new ProviderRegistrationInformation(new ProviderInfo());\n\n        private const int PumpFrequency = 100;\n\n        private const string ApacheNamespace = \"http://jakarta.apache.org/log4j/\";\n\n        private static readonly DateTime Log4JDateBase = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);\n\n        private static readonly ILog Log = LogManager.GetLogger(typeof(NLogViewerProvider));\n\n        private readonly XNamespace log4JNamespace = \"unique\";\n\n        private readonly XNamespace entryNamespace = \"nlogUnique\";\n\n        private readonly XNamespace apacheLog4JNamespace = ApacheNamespace;\n\n        private readonly Queue<string> pendingQueue = new Queue<string>();\n\n        private readonly INLogAppenderSettings networkSettings;\n\n        private CancellationTokenSource cancellationTokenSource;\n\n        private Task listenerTask;\n\n        public NLogViewerProvider(IProviderSettings settings)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            networkSettings = settings as INLogAppenderSettings;\n            networkSettings.ThrowIfNull(nameof(networkSettings));\n\n            Information = ProviderRegistrationInformation.Info;\n            ProviderSettings = networkSettings;\n        }\n\n        public IProviderInfo Information { get; private set; }\n\n        public IProviderSettings ProviderSettings { get; private set; }\n\n        public ILogger Logger { get; set; }\n\n        public string Name { get; set; }\n\n        public bool IsActive => listenerTask != null && listenerTask.Status == TaskStatus.Running;\n\n        public int Port { get; private set; }\n\n        public void Start()\n        {\n            Log.Debug(\"Start requested\");\n\n            if (listenerTask == null || listenerTask.IsCompleted)\n            {\n                cancellationTokenSource = new CancellationTokenSource();\n                var token = cancellationTokenSource.Token;\n\n                listenerTask = Task.Factory.StartNew(SocketListener, token);\n                Task.Factory.StartNew(MessagePump, token);\n            }\n            else\n            {\n                Log.WarnFormat(\n                    \"{0} listener task is already active and can not be started again.\",\n                    networkSettings.Protocol);\n            }\n        }\n\n        public void Pause()\n        {\n            Log.Debug(\"Pause requested\");\n            if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)\n            {\n                Log.Debug(\"Cancellation token triggered\");\n                cancellationTokenSource.Cancel();\n            }\n        }\n\n        public void Close()\n        {\n            Log.Debug(\"Close requested\");\n            if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested)\n            {\n                Log.Debug(\"Cancellation token triggered\");\n                cancellationTokenSource.Cancel();\n            }\n        }\n\n        private void SocketListener()\n        {\n            Log.Debug(\"SocketListener started\");\n\n            if (networkSettings == null)\n            {\n                Log.Error(\"Network settings has not been initialized\");\n                throw new NullReferenceException();\n            }\n\n            while (!cancellationTokenSource.IsCancellationRequested)\n            {\n                var endPoint = new IPEndPoint(IPAddress.Any, networkSettings.Port);\n\n                var networkProtocolDescription = networkSettings.Protocol.ToString();\n\n                using (var listener = new NetworkClientWrapper(networkSettings.Protocol, endPoint, cancellationTokenSource.Token))\n                {\n                    var remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);\n\n                    while (!cancellationTokenSource.IsCancellationRequested)\n                    {\n                        try\n                        {\n                            var bytes = listener.Receive(ref remoteEndPoint, 1000);\n\n                            if (Log.IsDebugEnabled)\n                                Log.DebugFormat(\n                                    \"Received {0} bytes from {1} ({2})\",\n                                    bytes.Length,\n                                    remoteEndPoint.Address,\n                                    networkProtocolDescription);\n\n                            var message = Encoding.UTF8.GetString(bytes, 0, bytes.Length);\n                            lock (pendingQueue)\n                            {\n                                pendingQueue.Enqueue(message);\n                            }\n                        }\n                        catch (SocketException socketException)\n                        {\n                            if (socketException.SocketErrorCode != SocketError.TimedOut)\n                            {\n                                Log.Error(\"SocketException\", socketException);\n                                Log.DebugFormat(\n                                    \"SocketException.SocketErrorCode = {0}\",\n                                    socketException.SocketErrorCode);\n\n                                // Break out of the 'using socket' loop and try to establish a new socket.\n                                break;\n                            }\n                        }\n                        catch (Exception e)\n                        {\n                            Log.Error(\"Network Exception\", e);\n                        }\n                    }\n                }\n            }\n\n            Log.Debug(\"SocketListener completed\");\n        }\n\n        private void MessagePump()\n        {\n            Log.Debug(\"MessagePump started\");\n\n            var processedQueue = new Queue<ILogEntry>();\n\n            while (!cancellationTokenSource.IsCancellationRequested)\n            {\n                Thread.Sleep(PumpFrequency);\n\n                try\n                {\n                    if (Logger != null)\n                    {\n                        lock (pendingQueue)\n                        {\n                            while (pendingQueue.Count > 0)\n                            {\n                                var message = pendingQueue.Dequeue();\n\n                                if (IsValidEntry(message))\n                                {\n                                    var deserializeMessage = DecodeEntry(message);\n\n                                    if (deserializeMessage != null)\n                                    {\n                                        processedQueue.Enqueue(deserializeMessage);\n                                    }\n                                }\n                            }\n                        }\n\n                        if (processedQueue.Any())\n                        {\n                            Logger.AddBatch(processedQueue);\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    Log.Error(\"MessagePump Exception\", e);\n                }\n                finally\n                {\n                    processedQueue.Clear();\n                }\n            }\n\n            Log.Debug(\"MessagePump completed\");\n        }\n\n        private bool IsValidEntry(string logEntry)\n        {\n            return logEntry.StartsWith(\"<log4j\");\n        }\n\n        private LogEntry DecodeEntry(string m)\n        {\n            // Record the current date/time\n            var receivedTime = DateTime.UtcNow;\n\n            var message = $@\"<entry xmlns:log4j=\"\"{log4JNamespace}\"\" xmlns:nlog=\"\"{entryNamespace}\"\">{m}</entry>\";\n            var element = XElement.Parse(message);\n\n            // serilog nlogFormatting uses an explicit namespace, detect it when used.\n            var eventNamespace = message.Contains(ApacheNamespace) ? apacheLog4JNamespace : log4JNamespace;\n            var @event = element.Element(eventNamespace + \"event\");\n            if (@event == null)\n            {\n                return null;\n            }\n\n            // Establish whether a sub-system seems to be defined.\n            var description = @event.Element(eventNamespace + \"message\")?.Value ?? string.Empty;\n\n            var classification = string.Empty;\n            var system = @event.Attribute(\"logger\")?.Value ?? string.Empty;\n            var type = @event.Attribute(\"level\")?.Value ?? string.Empty;\n            var host = \"Unknown\";\n\n            var meta = new Dictionary<string, object>();\n\n            foreach (var propertyElement in @event.Element(eventNamespace + \"properties\")?.Elements()\n                                            ?? Enumerable.Empty<XElement>())\n            {\n                if (propertyElement.Name == eventNamespace + \"data\")\n                {\n                    var name = propertyElement.Attribute(\"name\")?.Value;\n                    var value = propertyElement.Attribute(\"value\")?.Value;\n\n                    if (name == \"log4jmachinename\")\n                    {\n                        host = value;\n                    }\n                    else if (!string.IsNullOrWhiteSpace(name))\n                    {\n                        if (meta.ContainsKey(name))\n                        {\n                            Log.Warn($\"Already have property of {name}, overwriting\");\n                        }\n\n                        meta.Add(name, value);\n                    }\n                }\n            }\n\n            var source = @event.Element(eventNamespace + \"locationInfo\");\n\n            // Any source information\n            var className = source?.Attribute(\"class\")?.Value ?? string.Empty;\n            var methodName = source?.Attribute(\"method\")?.Value ?? string.Empty;\n            var sourceFile = source?.Attribute(\"file\")?.Value ?? string.Empty;\n            var line = source?.Attribute(\"line\")?.Value ?? string.Empty;\n\n            var timestampValue = @event.Attribute(\"timestamp\")?.Value;\n            var date = DateTime.UtcNow;\n            if (timestampValue != null)\n            {\n                var timestamp = double.Parse(timestampValue);\n                date = Log4JDateBase + TimeSpan.FromMilliseconds(timestamp);\n            }\n\n            meta[\"Classification\"] = classification;\n            meta[\"Host\"] = host;\n\n            var entry = new LogEntry\n                            {\n                                DateTime = date,\n                                System = system,\n                                Thread = @event.Attribute(\"thread\")?.Value ?? string.Empty,\n                                Description = description,\n                                Type = type,\n                                MetaData = meta,\n                            };\n\n            // Determine whether this constitutes an exception\n            var throwable = @event.Element(eventNamespace + \"throwable\");\n            if (throwable != null)\n            {\n                entry.MetaData.Add(\"Exception\", throwable.Value);\n            }\n            else if (entry.Description.ToUpper().Contains(\"EXCEPTION\"))\n            {\n                entry.MetaData.Add(\"Exception\", true);\n            }\n\n            if (!string.IsNullOrWhiteSpace(className))\n            {\n                // TODO: use an object for these?\n                meta.Add(\"ClassName\", className);\n                meta.Add(\"MethodName\", methodName);\n                meta.Add(\"SourceFile\", sourceFile);\n                meta.Add(\"SourceLine\", line);\n            }\n\n            meta.Add(\"ReceivedTime\", receivedTime);\n\n            return entry;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/NetworkClientWrapper.cs",
    "content": "namespace Sentinel.NLog\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Linq;\n    using System.Net;\n    using System.Net.Sockets;\n\n    public class NetworkClientWrapper : IDisposable\n    {\n        private readonly bool isUdp;\n\n        private readonly UdpClient udpClient;\n\n        private readonly TcpListener tcpListener;\n\n        private NetworkStream activeTcpStream;\n\n        public NetworkClientWrapper(NetworkProtocol protocol, IPEndPoint endPoint, System.Threading.CancellationToken cancellationToken)\n        {\n            isUdp = protocol == NetworkProtocol.Udp;\n            if (isUdp)\n            {\n                udpClient = new UdpClient { ExclusiveAddressUse = false };\n                udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);\n                udpClient.Client.Bind(endPoint);\n            }\n            else\n            {\n                tcpListener = new TcpListener(endPoint);\n                tcpListener.Start();\n                cancellationToken.Register(() => tcpListener.Stop());\n            }\n        }\n\n        public byte[] Receive(ref IPEndPoint remoteEndPoint, int receiveTimeout)\n        {\n            if (isUdp)\n            {\n                udpClient.Client.ReceiveTimeout = receiveTimeout;\n                return udpClient.Receive(ref remoteEndPoint);\n            }\n\n            var returnBuffer = new List<byte>(100);\n\n            NetworkStream networkStream = null;\n\n            try\n            {\n                networkStream = activeTcpStream ?? (activeTcpStream = tcpListener.AcceptTcpClient().SetReceiveTimeout(receiveTimeout).GetStream());\n\n                var buffer = new byte[1];\n                while (networkStream.Read(buffer, 0, buffer.Length) != 0)\n                {\n                    if (buffer[0] == (byte)'\\n')\n                        break;\n                    if (buffer[0] == (byte)'\\r')\n                        continue;\n\n                    returnBuffer.Add(buffer[0]);\n                }\n            }\n            catch\n            {\n                activeTcpStream = null;\n                networkStream?.Close();\n                throw;\n            }\n\n            return returnBuffer.ToArray();\n        }\n\n        public void Dispose()\n        {\n            GC.SuppressFinalize(this);\n\n            udpClient?.Close();\n            activeTcpStream?.Close();\n            tcpListener?.Stop();\n        }\n    }\n\n    internal static class TcpClientExtensions\n    {\n        public static TcpClient SetReceiveTimeout(this TcpClient tcpClient, int receiveTimeout)\n        {\n            tcpClient.Client.ReceiveTimeout = receiveTimeout;\n            return tcpClient;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/NetworkConfigurationPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.NLog.NetworkConfigurationPage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n             mc:Ignorable=\"d\" \n             d:DesignHeight=\"450\" d:DesignWidth=\"800\">\n    <UserControl.Resources>\n        <converters:BooleanInvertingValueConverter x:Key=\"InvertConverter\" />\n        <converters:VisibilityToHiddenConverter x:Key=\"visToHiddenConverter\" />\n    </UserControl.Resources>\n\n    <Grid Margin=\"3\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"40\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n\n        <TextBlock Text=\"Protocol : \"\n                   Margin=\"3\"\n                   VerticalAlignment=\"Top\" />\n        <StackPanel Grid.Row=\"0\"\n                    Grid.Column=\"1\">\n            <RadioButton Content=\"UDP\"\n                         Margin=\"3\"\n                         IsEnabled=\"{Binding SupportsUdp}\"\n                         IsChecked=\"{Binding IsUdp}\" />\n            <RadioButton Content=\"TCP\"\n                         Margin=\"3\"\n                         IsEnabled=\"{Binding SupportsTcp}\"\n                         IsChecked=\"{Binding IsUdp, Converter={StaticResource InvertConverter}}\" />\n        </StackPanel>\n\n        <TextBlock Margin=\"3\"\n                   Foreground=\"Red\"\n                   Text=\"Caution: setting software up for TCP may have a performance impact.\"\n                   Grid.Row=\"1\"\n                   Grid.Column=\"1\"\n                   VerticalAlignment=\"Top\"\n                   Visibility=\"{Binding IsUdp, Converter={StaticResource visToHiddenConverter}, ConverterParameter=true}\"\n                   TextWrapping=\"WrapWithOverflow\" />\n\n        <TextBlock Text=\"Port : \"\n                   Margin=\"3\"\n                   Grid.Row=\"2\"\n                   VerticalAlignment=\"Center\" />\n        <TextBox x:Name=\"portText\"\n                 Margin=\"3\"\n                 Text=\"{Binding Port, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}\"\n                 Grid.Row=\"2\"\n                 Grid.Column=\"1\"\n                 VerticalAlignment=\"Center\" />\n        <TextBlock Margin=\"3\"\n                   Grid.Row=\"3\"\n                   Grid.Column=\"1\"\n                   VerticalAlignment=\"Top\"\n                   TextWrapping=\"WrapWithOverflow\">\n            <Run>Please note that depending upon your firewall settings, you may be prompted to confirm the openning of a network port.</Run>\n            <LineBreak />\n            <Run>This normally only occurs the first time.</Run>\n        </TextBlock>\n    </Grid>\n\n</UserControl>\n"
  },
  {
    "path": "Sentinel/NLog/NetworkConfigurationPage.xaml.cs",
    "content": "﻿namespace Sentinel.NLog\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Windows.Controls;\n\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.WpfExtras;\n\n    public partial class NetworkConfigurationPage : UserControl, IWizardPage\n    {\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private bool isValid;\n\n        private int port;\n\n        private bool isUdp = true;\n\n        public NetworkConfigurationPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            // Register to self so that we can handler user interactions.\n            PropertyChanged += SelectProviderPage_PropertyChanged;\n\n            Port = 9999;\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public virtual bool SupportsTcp => true;\n\n        public virtual bool SupportsUdp => true;\n\n        public int Port\n        {\n            get => port;\n            set\n            {\n                if (port != value)\n                {\n                    port = value;\n                    OnPropertyChanged(nameof(Port));\n                }\n            }\n        }\n\n        public bool IsUdp\n        {\n            get => isUdp;\n            set\n            {\n                if (isUdp != value)\n                {\n                    isUdp = value;\n                    OnPropertyChanged(nameof(IsUdp));\n                }\n            }\n        }\n\n        public string Title => \"Configure Provider\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public string Description => \"Network settings to be used by new provider\";\n\n        public bool IsValid\n        {\n            get => isValid;\n\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent => this;\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n            OnPropertyChanged(nameof(Children));\n        }\n\n        public object Save(object saveData)\n        {\n            Debug.Assert(\n                saveData != null,\n                \"Expecting the save-data component to have details from the previous pages.\");\n            Debug.Assert(\n                saveData is IProviderSettings,\n                \"Expecting the save-data component to be of an IProviderSettings type.\");\n\n            var previousInfo = (IProviderSettings) saveData;\n\n            return new NetworkSettings\n            {\n                Name = previousInfo.Name,\n                Info = previousInfo.Info,\n                Port = Port,\n                Protocol = IsUdp ? NetworkProtocol.Udp : NetworkProtocol.Tcp,\n            };\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void OnLoaded(object sender, System.Windows.RoutedEventArgs e)\n        {\n            // Establish default selection\n            Debug.Assert(SupportsUdp || SupportsTcp, \"The provider needs to support at least one of UDP or TCP\");\n            IsUdp = SupportsUdp;\n        }\n\n        private void SelectProviderPage_PropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"Port\")\n            {\n                bool state = port > 2000;\n                Trace.WriteLine($\"Setting PageValidates to {state}\");\n                IsValid = state;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/NLog/NetworkProtocol.cs",
    "content": "namespace Sentinel.NLog\n{\n    public enum NetworkProtocol\n    {\n        /// <summary>\n        /// Use UDP protocol\n        /// </summary>\n        Udp,\n\n        /// <summary>\n        /// Use TCP protocol\n        /// </summary>\n        Tcp,\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/NetworkSettings.cs",
    "content": "﻿namespace Sentinel.NLog\n{\n    using System.Runtime.Serialization;\n\n    [DataContract]\n    public class NetworkSettings : ProviderSettings, INLogAppenderSettings\n    {\n        public NetworkSettings()\n        {\n            Port = 9999;\n            Protocol = NetworkProtocol.Udp;\n        }\n\n        public NetworkProtocol Protocol { get; set; }\n\n        public int Port { get; set; }\n\n        public override string Summary\n        {\n            get\n            {\n                return $\"{Name}: Listens on {Protocol} port {Port}\";\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/ProviderInfo.cs",
    "content": "namespace Sentinel.NLog\n{\n    using System;\n\n    using Sentinel.Interfaces.Providers;\n\n    internal class ProviderInfo : IProviderInfo\n    {\n        public Guid Identifier => new Guid(\"F12581A5-64C0-4B35-91FC-81C9A09C1E0B\");\n\n        public string Name => \"NLog Viewer Provider\";\n\n        public string Description => \"Handler for nLog's log4j networking protocol log target.\";\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/ProviderRegistrationInformation.cs",
    "content": "namespace Sentinel.NLog\n{\n    using System;\n\n    using Sentinel.Interfaces.Providers;\n\n    public class ProviderRegistrationInformation : IProviderRegistrationRecord\n    {\n        public ProviderRegistrationInformation(IProviderInfo providerInfo)\n        {\n            Info = providerInfo;\n        }\n\n        public Guid Identifier => Info.Identifier;\n\n        public IProviderInfo Info { get; private set; }\n\n        public Type Settings => typeof(NetworkConfigurationPage);\n\n        public Type Implementer => typeof(NLogViewerProvider);\n    }\n}"
  },
  {
    "path": "Sentinel/NLog/ProviderSettings.cs",
    "content": "﻿namespace Sentinel.NLog\n{\n    using System.Runtime.Serialization;\n\n    using Sentinel.Interfaces.Providers;\n\n    [DataContract]\n    public class ProviderSettings : IProviderSettings\n    {\n        public string Name { get; set; }\n\n        public virtual string Summary => $\"Provider named {Name}\";\n\n        /// <summary>\n        /// Reference back to the provider this setting is appropriate to.\n        /// </summary>\n        public IProviderInfo Info { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Preferences/UserPreferences.cs",
    "content": "﻿namespace Sentinel.Preferences\n{\n    using System.Collections.Generic;\n    using System.Runtime.Serialization;\n    using log4net;\n    using Sentinel.Interfaces;\n    using Sentinel.Support.Wpf;\n    using WpfExtras;\n\n    /// <summary>\n    /// An implementation of the IUserPreferences which holds all of the user\n    /// selections in a view-model based structure, allowing simple binding to\n    /// the contents for GUIs whilst also allowing other interested parties to\n    /// register to elements to be notified when they change.\n    /// </summary>\n    [DataContract]\n    public class UserPreferences : ViewModelBase, IUserPreferences\n    {\n        private static readonly ILog Log = LogManager.GetLogger(nameof(UserPreferences));\n\n        private int selectedDateOption;\n\n        private int selectedTimeFormatOption;\n\n        private bool useArrivalDateTime;\n\n        private bool convertUtcTimesToLocalTimeZone = true;\n\n        private int selectedTypeOption = 1;\n\n        private bool show;\n\n        private bool showThreadColumn;\n\n        private bool showExceptionColumn;\n\n        private bool showSourceColumn;\n\n        private bool useLazyRebuild;\n\n        private bool useStackedLayout = true;\n\n        private bool useTighterRows = true;\n\n        private bool doubleClickToShowExceptions = true;\n\n        private bool showSourceInformationColumns;\n\n        private bool showContextColumn;\n\n        private string contextProperty = \"UnitTest\";\n\n        private bool enableClearCommand = true;\n\n        private string clearCommandMatchText = \"#!Clear\";\n\n        private bool limitMessages = false;\n\n        private string maximumMessageCount;\n\n        public UserPreferences()\n        {\n            PropertyChanged += (s, a) =>\n            {\n                if (a.PropertyName == nameof(ContextProperty))\n                {\n                    Log.Debug($\"{a.PropertyName}={ContextProperty}\");\n                }\n            };\n        }\n\n        /// <summary>\n        /// Gets the name of the current Windows theme.\n        /// </summary>\n        public string CurrentThemeName => ThemeInfo.CurrentThemeFileName;\n\n        /// <summary>\n        /// Gets an enumerable list of the available date formatting options.\n        /// </summary>\n        public IEnumerable<string> DateFormatOptions { get; } = new[]\n        {\n            \"yyyy-MM-dd\",\n            \"MMM-dd\",\n            \"dd-MM-yyyy\",\n            \"dd-MMM\",\n            \"MM-dd-yyyy\",\n            \"dddd\",\n        };\n\n        public IEnumerable<string> TimeFormatOptions { get; } = new[] { \"HH:mm:ss;FFFF\", \"HH:mm:ss\", \"HH:mm\" };\n\n        /// <summary>\n        /// Gets or sets the selected date option, as a index of the available options.\n        /// </summary>\n        /// <see cref=\"DateFormatOptions\"/>\n        public int SelectedDateOption\n        {\n            get => selectedDateOption;\n\n            set\n            {\n                if (selectedDateOption != value)\n                {\n                    selectedDateOption = value;\n                    OnPropertyChanged(nameof(SelectedDateOption));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets the selected time format, as a index of the available options.\n        /// </summary>\n        /// <see cref=\"DateFormatOptions\"/>\n        public int SelectedTimeFormatOption\n        {\n            get => selectedTimeFormatOption;\n\n            set\n            {\n                if (selectedTimeFormatOption != value)\n                {\n                    selectedTimeFormatOption = value;\n                    OnPropertyChanged(nameof(SelectedTimeFormatOption));\n                }\n            }\n        }\n\n        public bool ConvertUtcTimesToLocalTimeZone\n        {\n            get => convertUtcTimesToLocalTimeZone;\n\n            set\n            {\n                if (value != convertUtcTimesToLocalTimeZone)\n                {\n                    convertUtcTimesToLocalTimeZone = value;\n                    OnPropertyChanged(nameof(ConvertUtcTimesToLocalTimeZone));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the display should be of the parsed date/time or of the one made upon message receipt.\n        /// </summary>\n        public bool UseArrivalDateTime\n        {\n            get => useArrivalDateTime;\n\n            set\n            {\n                if (useArrivalDateTime != value)\n                {\n                    useArrivalDateTime = value;\n                    OnPropertyChanged(nameof(UseArrivalDateTime));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets the selected type option, as a index of the available options.\n        /// </summary>\n        /// <seealso cref=\"TypeOptions\"/>\n        public int SelectedTypeOption\n        {\n            get => selectedTypeOption;\n\n            set\n            {\n                if (selectedTypeOption != value)\n                {\n                    selectedTypeOption = value;\n                    OnPropertyChanged(nameof(SelectedTypeOption));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the user preferences should be shown.\n        /// </summary>\n        public bool Show\n        {\n            get => show;\n\n            set\n            {\n                if (show != value)\n                {\n                    show = value;\n                    OnPropertyChanged(nameof(Show));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the thread column should be shown or not.\n        /// </summary>\n        public bool ShowThreadColumn\n        {\n            get => showThreadColumn;\n\n            set\n            {\n                if (showThreadColumn != value)\n                {\n                    showThreadColumn = value;\n                    OnPropertyChanged(nameof(ShowThreadColumn));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the thread column should be shown or not.\n        /// </summary>\n        public bool ShowSourceColumn\n        {\n            get => showSourceColumn;\n\n            set\n            {\n                if (showSourceColumn != value)\n                {\n                    showSourceColumn = value;\n                    OnPropertyChanged(nameof(ShowSourceColumn));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the exception column should be shown or not.\n        /// </summary>\n        public bool ShowExceptionColumn\n        {\n            get => showExceptionColumn;\n\n            set\n            {\n                if (showExceptionColumn != value)\n                {\n                    showExceptionColumn = value;\n                    OnPropertyChanged(nameof(ShowExceptionColumn));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets a list of the available type column options, such as hidden, icons, text, etc.\n        /// </summary>\n        public IEnumerable<string> TypeOptions => new[] { \"Hidden\", \"Icons\", \"Text\", \"Icon and text\" };\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the lazy rebuilding option should be used\n        /// for rebuilding sorted views.\n        /// </summary>\n        /// <remarks>\n        /// Lazy in this case means throwing away the existing collection and building\n        /// a new one.  This isn't optimal in terms of memory nor functionality, things such as\n        /// selected index in a data bound ListView can't be maintained for long.\n        /// Visually, this works around an issue, but at the expense performance, memory, etc.\n        /// </remarks>\n        public bool UseLazyRebuild\n        {\n            get => useLazyRebuild;\n\n            set\n            {\n                if (useLazyRebuild != value)\n                {\n                    useLazyRebuild = value;\n                    OnPropertyChanged(nameof(UseLazyRebuild));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the log messages and activity windows should be\n        /// displayed with on top of each other (stacked) or beside each other.\n        /// </summary>\n        public bool UseStackedLayout\n        {\n            get => useStackedLayout;\n\n            set\n            {\n                if (useStackedLayout != value)\n                {\n                    useStackedLayout = value;\n                    OnPropertyChanged(nameof(UseStackedLayout));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether a visual padding correction should be used to\n        /// tighten the rows in a list view.  Windows Vista and Windows 7 both use much more padding\n        /// around each row than Windows XP does.  Sometimes the XP look works better.\n        /// </summary>\n        public bool UseTighterRows\n        {\n            get => useTighterRows;\n\n            set\n            {\n                if (useTighterRows != value)\n                {\n                    useTighterRows = value;\n                    OnPropertyChanged(nameof(UseTighterRows));\n                }\n            }\n        }\n\n        public bool DoubleClickToShowExceptions\n        {\n            get => doubleClickToShowExceptions;\n\n            set\n            {\n                if (doubleClickToShowExceptions != value)\n                {\n                    doubleClickToShowExceptions = value;\n                    OnPropertyChanged(nameof(DoubleClickToShowExceptions));\n                }\n            }\n        }\n\n        public bool ShowSourceInformationColumns\n        {\n            get => showSourceInformationColumns;\n\n            set\n            {\n                if (showSourceInformationColumns != value)\n                {\n                    showSourceInformationColumns = value;\n                    OnPropertyChanged(nameof(ShowSourceInformationColumns));\n                }\n            }\n        }\n\n        public bool ShowContextColumn\n        {\n            get => showContextColumn;\n\n            set\n            {\n                if (showContextColumn != value)\n                {\n                    showContextColumn = value;\n                    OnPropertyChanged(nameof(ShowContextColumn));\n                }\n            }\n        }\n\n        public string ContextProperty\n        {\n            get => contextProperty;\n\n            set\n            {\n                if (contextProperty != value)\n                {\n                    contextProperty = value;\n                    OnPropertyChanged(nameof(ContextProperty));\n                }\n            }\n        }\n\n        public bool EnableClearCommand\n        {\n            get => enableClearCommand;\n            set\n            {\n                if (enableClearCommand != value)\n                {\n                    enableClearCommand = value;\n                    OnPropertyChanged(nameof(EnableClearCommand));\n                }\n            }\n        }\n\n        public string ClearCommandMatchText\n        {\n            get => clearCommandMatchText;\n            set\n            {\n                if (clearCommandMatchText != value)\n                {\n                    clearCommandMatchText = value;\n                    OnPropertyChanged(nameof(ClearCommandMatchText));\n                }\n            }\n        }\n\n        public bool LimitMessages\n        {\n            get => limitMessages;\n            set\n            {\n                if (value != limitMessages)\n                {\n                    limitMessages = value;\n                    OnPropertyChanged(nameof(LimitMessages));\n                }\n            }\n        }\n\n        public string MaximumMessageCount\n        {\n            get => maximumMessageCount;\n            set\n            {\n                if (maximumMessageCount != value)\n                {\n                    maximumMessageCount = value;\n                    OnPropertyChanged(nameof(MaximumMessageCount));\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.InteropServices;\nusing System.Windows;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyDescription(\"To watch over as a guard and provide real-time organization of logging sessions.\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// In order to begin building localizable applications, set\n// <UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file\n// inside a <PropertyGroup>.  For example, if you are using US english\n// in your source files, set the <UICulture> to en-US.  Then uncomment\n// the NeutralResourceLanguage attribute below.  Update the \"en-US\" in\n// the line below to match the UICulture setting in the project file.\n// [assembly: NeutralResourcesLanguage(\"en-US\", UltimateResourceFallbackLocation.Satellite)]\n[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]\n"
  },
  {
    "path": "Sentinel/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\r\n// <auto-generated>\r\n//     This code was generated by a tool.\r\n//     Runtime Version:4.0.30319.42000\r\n//\r\n//     Changes to this file may cause incorrect behavior and will be lost if\r\n//     the code is regenerated.\r\n// </auto-generated>\r\n//------------------------------------------------------------------------------\r\n\r\nnamespace Sentinel.Properties {\r\n    using System;\r\n    \r\n    \r\n    /// <summary>\r\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\r\n    /// </summary>\r\n    // This class was auto-generated by the StronglyTypedResourceBuilder\r\n    // class via a tool like ResGen or Visual Studio.\r\n    // To add or remove a member, edit your .ResX file then rerun ResGen\r\n    // with the /str option, or rebuild your VS project.\r\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"16.0.0.0\")]\r\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\r\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\r\n    internal class Resources {\r\n        \r\n        private static global::System.Resources.ResourceManager resourceMan;\r\n        \r\n        private static global::System.Globalization.CultureInfo resourceCulture;\r\n        \r\n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\r\n        internal Resources() {\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Returns the cached ResourceManager instance used by this class.\r\n        /// </summary>\r\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\r\n        internal static global::System.Resources.ResourceManager ResourceManager {\r\n            get {\r\n                if (object.ReferenceEquals(resourceMan, null)) {\r\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"Sentinel.Properties.Resources\", typeof(Resources).Assembly);\r\n                    resourceMan = temp;\r\n                }\r\n                return resourceMan;\r\n            }\r\n        }\r\n        \r\n        /// <summary>\r\n        ///   Overrides the current thread's CurrentUICulture property for all\r\n        ///   resource lookups using this strongly typed resource class.\r\n        /// </summary>\r\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\r\n        internal static global::System.Globalization.CultureInfo Culture {\r\n            get {\r\n                return resourceCulture;\r\n            }\r\n            set {\r\n                resourceCulture = value;\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "Sentinel/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<root>\r\n  <!-- \r\n    Microsoft ResX Schema \r\n    \r\n    Version 2.0\r\n    \r\n    The primary goals of this format is to allow a simple XML format \r\n    that is mostly human readable. The generation and parsing of the \r\n    various data types are done through the TypeConverter classes \r\n    associated with the data types.\r\n    \r\n    Example:\r\n    \r\n    ... ado.net/XML headers & schema ...\r\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\r\n    <resheader name=\"version\">2.0</resheader>\r\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\r\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\r\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\r\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\r\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\r\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\r\n    </data>\r\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\r\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\r\n        <comment>This is a comment</comment>\r\n    </data>\r\n                \r\n    There are any number of \"resheader\" rows that contain simple \r\n    name/value pairs.\r\n    \r\n    Each data row contains a name, and value. The row also contains a \r\n    type or mimetype. Type corresponds to a .NET class that support \r\n    text/value conversion through the TypeConverter architecture. \r\n    Classes that don't support this are serialized and stored with the \r\n    mimetype set.\r\n    \r\n    The mimetype is used for serialized objects, and tells the \r\n    ResXResourceReader how to depersist the object. This is currently not \r\n    extensible. For a given mimetype the value must be set accordingly:\r\n    \r\n    Note - application/x-microsoft.net.object.binary.base64 is the format \r\n    that the ResXResourceWriter will generate, however the reader can \r\n    read any of the formats listed below.\r\n    \r\n    mimetype: application/x-microsoft.net.object.binary.base64\r\n    value   : The object must be serialized with \r\n            : System.Serialization.Formatters.Binary.BinaryFormatter\r\n            : and then encoded with base64 encoding.\r\n    \r\n    mimetype: application/x-microsoft.net.object.soap.base64\r\n    value   : The object must be serialized with \r\n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\r\n            : and then encoded with base64 encoding.\r\n\r\n    mimetype: application/x-microsoft.net.object.bytearray.base64\r\n    value   : The object must be serialized into a byte array \r\n            : using a System.ComponentModel.TypeConverter\r\n            : and then encoded with base64 encoding.\r\n    -->\r\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\r\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\r\n      <xsd:complexType>\r\n        <xsd:choice maxOccurs=\"unbounded\">\r\n          <xsd:element name=\"metadata\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"assembly\">\r\n            <xsd:complexType>\r\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"data\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" msdata:Ordinal=\"1\" />\r\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\r\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n          <xsd:element name=\"resheader\">\r\n            <xsd:complexType>\r\n              <xsd:sequence>\r\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\r\n              </xsd:sequence>\r\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\r\n            </xsd:complexType>\r\n          </xsd:element>\r\n        </xsd:choice>\r\n      </xsd:complexType>\r\n    </xsd:element>\r\n  </xsd:schema>\r\n  <resheader name=\"resmimetype\">\r\n    <value>text/microsoft-resx</value>\r\n  </resheader>\r\n  <resheader name=\"version\">\r\n    <value>2.0</value>\r\n  </resheader>\r\n  <resheader name=\"reader\">\r\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n  <resheader name=\"writer\">\r\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\r\n  </resheader>\r\n</root>"
  },
  {
    "path": "Sentinel/Properties/Settings.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\r\n// <auto-generated>\r\n//     This code was generated by a tool.\r\n//     Runtime Version:4.0.30319.42000\r\n//\r\n//     Changes to this file may cause incorrect behavior and will be lost if\r\n//     the code is regenerated.\r\n// </auto-generated>\r\n//------------------------------------------------------------------------------\r\n\r\nnamespace Sentinel.Properties {\r\n    \r\n    \r\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\r\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator\", \"16.7.0.0\")]\r\n    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {\r\n        \r\n        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));\r\n        \r\n        public static Settings Default {\r\n            get {\r\n                return defaultInstance;\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "Sentinel/Properties/Settings.settings",
    "content": "﻿<?xml version='1.0' encoding='utf-8'?>\r\n<SettingsFile xmlns=\"uri:settings\" CurrentProfile=\"(Default)\">\r\n  <Profiles>\r\n    <Profile Name=\"(Default)\" />\r\n  </Profiles>\r\n  <Settings />\r\n</SettingsFile>"
  },
  {
    "path": "Sentinel/Properties/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <assemblyIdentity version=\"1.0.0.0\" name=\"MyApplication.app\" />\n  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v2\">\n    <security>\n      <requestedPrivileges xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n        <!-- UAC Manifest Options\n             If you want to change the Windows User Account Control level replace the \n             requestedExecutionLevel node with one of the following.\n\n        <requestedExecutionLevel  level=\"asInvoker\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"requireAdministrator\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"highestAvailable\" uiAccess=\"false\" />\n\n            Specifying requestedExecutionLevel element will disable file and registry virtualization. \n            Remove this element if your application requires this virtualization for backwards\n            compatibility.\n        -->\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />\n      </requestedPrivileges>\n      <applicationRequestMinimum>\n        <PermissionSet class=\"System.Security.PermissionSet\" version=\"1\" Unrestricted=\"true\" ID=\"Custom\" SameSite=\"site\" />\n        <defaultAssemblyRequest permissionSetReference=\"Custom\" />\n      </applicationRequestMinimum>\n    </security>\n  </trustInfo>\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- A list of the Windows versions that this application has been tested on and is\n           is designed to work with. Uncomment the appropriate elements and Windows will \n           automatically selected the most compatible environment. -->\n      <!-- Windows Vista -->\n      <!--<supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" />-->\n      <!-- Windows 7 -->\n      <!--<supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />-->\n      <!-- Windows 8 -->\n      <!--<supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />-->\n      <!-- Windows 8.1 -->\n      <!--<supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" />-->\n      <!-- Windows 10 -->\n      <!--<supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />-->\n    </application>\n  </compatibility>\n  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher\n       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need \n       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should \n       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->\n  <!--\n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    <windowsSettings>\n      <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true</dpiAware>\n    </windowsSettings>\n  </application>\n  -->\n  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->\n  <!--\n  <dependency>\n    <dependentAssembly>\n      <assemblyIdentity\n          type=\"win32\"\n          name=\"Microsoft.Windows.Common-Controls\"\n          version=\"6.0.0.0\"\n          processorArchitecture=\"*\"\n          publicKeyToken=\"6595b64144ccf1df\"\n          language=\"*\"\n        />\n    </dependentAssembly>\n  </dependency>\n  -->\n</assembly>"
  },
  {
    "path": "Sentinel/Providers/Interfaces/INewProviderWizard.cs",
    "content": "﻿namespace Sentinel.Providers.Interfaces\n{\n    using System.Windows;\n\n    using Sentinel.Interfaces.Providers;\n\n    public interface INewProviderWizard\n    {\n        IProviderInfo Provider { get; }\n\n        IProviderSettings Settings { get; }\n\n        bool Display(Window parent);\n    }\n}"
  },
  {
    "path": "Sentinel/Providers/Interfaces/IProviderManager.cs",
    "content": "namespace Sentinel.Providers.Interfaces\r\n{\r\n    using System;\r\n    using System.Collections.Generic;\r\n\r\n    using Sentinel.Interfaces.Providers;\r\n\r\n    public interface IProviderManager : IEnumerable<Guid>\r\n    {\r\n        IEnumerable<ILogProvider> Instances { get; }\r\n\r\n        IEnumerable<Guid> Registered { get; }\r\n\r\n        void Register(IProviderRegistrationRecord record);\r\n\r\n        ILogProvider Create(Guid providerGuid, IProviderSettings settings);\r\n\r\n        ILogProvider Get(string name);\r\n\r\n        void Remove(string name);\r\n\r\n        IProviderInfo GetInformation(Guid providerGuid);\r\n\r\n        T GetConfiguration<T>(Guid providerGuid);\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Providers/Interfaces/PendingProviderRecord.cs",
    "content": "﻿namespace Sentinel.Providers.Interfaces\r\n{\r\n    using Sentinel.Interfaces.Providers;\r\n\r\n    public class PendingProviderRecord\r\n    {\r\n        public IProviderInfo Info { get; set; }\r\n\r\n        public IProviderSettings Settings { get; set; }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Providers/NewProviderWizard.cs",
    "content": "﻿namespace Sentinel.Providers\n{\n    using System;\n    using System.Windows;\n\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.Providers.Interfaces;\n\n    using WpfExtras;\n\n    public class NewProviderWizard : INewProviderWizard\n    {\n        public IProviderInfo Provider { get; private set; }\n\n        public IProviderSettings Settings { get; private set; }\n\n        public bool Display(Window parent)\n        {\n            IProviderSettings settings = new ProviderSettings();\n\n            // Construct the wizard\n            var wizard = new Wizard\n                             {\n                                 Owner = parent,\n                                 ShowNavigationTree = false,\n                                 SavedData = settings,\n                                 Title = \"Add New Log Provider\",\n                             };\n\n            wizard.AddPage(new SelectProviderPage());\n\n            var dialogResult = wizard.ShowDialog();\n            if (dialogResult == true)\n            {\n                if (wizard.SavedData == null && !(wizard.SavedData is IProviderSettings))\n                {\n                    throw new NotImplementedException(\n                        \"The UserData was either null or the supplied object was not of the expected type: IProviderSettings\");\n                }\n\n                Settings = (IProviderSettings)wizard.SavedData;\n                Provider = Settings.Info;\n            }\n\n            return dialogResult ?? false;\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Providers/ProviderInfo.cs",
    "content": "namespace Sentinel.Providers\r\n{\r\n    using System;\r\n\r\n    using Sentinel.Interfaces.Providers;\r\n\r\n    public class ProviderInfo : IProviderInfo\r\n    {\r\n        public ProviderInfo(Guid uniqueId, string name, string description)\r\n        {\r\n            Identifier = uniqueId;\r\n            Name = name;\r\n            Description = description;\r\n        }\r\n\r\n        public Guid Identifier { get; private set; }\r\n\r\n        public string Name { get; private set; }\r\n\r\n        public string Description { get; private set; }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Providers/ProviderManager.cs",
    "content": "﻿namespace Sentinel.Providers\n{\n    using System;\n    using System.Collections;\n    using System.Collections.Generic;\n    using System.Diagnostics;\n    using System.Linq;\n\n    using Sentinel.FileMonitor;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.Log4Net;\n    using Sentinel.MSBuild;\n    using Sentinel.NLog;\n    using Sentinel.Providers.Interfaces;\n\n    public class ProviderManager : IProviderManager\n    {\n        private readonly IList<IProviderRegistrationRecord> providers;\n\n        private readonly List<KeyValuePair<string, ILogProvider>> providerInstances =\n            new List<KeyValuePair<string, ILogProvider>>();\n\n        public ProviderManager()\n        {\n            providers = new List<IProviderRegistrationRecord>\n                            {\n                                NLogViewerProvider.ProviderRegistrationInformation,\n                                Log4NetProvider.ProviderRegistrationInformation,\n                                FileMonitoringProvider.ProviderRegistrationInformation,\n                                MsBuildProvider.ProviderRegistrationRecord,\n                            };\n        }\n\n        public IEnumerable<Guid> Registered => providers.Select(p => p.Identifier);\n\n        public IEnumerable<ILogProvider> Instances => providerInstances.Select(i => i.Value);\n\n        public void Register(IProviderRegistrationRecord record)\n        {\n            throw new NotImplementedException(\"Dynamic registration is not yet supported\");\n        }\n\n        public ILogProvider Create(Guid providerGuid, IProviderSettings settings)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            // Make sure we don't have any instances of that providerGuid.\n            if (providerInstances.Any(p => p.Key == settings.Name && p.Value.Information.Identifier == providerGuid))\n            {\n                throw new ArgumentException(\n                    \"Already an instance of that ILoggerProvider with that name specified\",\n                    nameof(settings));\n            }\n\n            // Make sure that the type is supported.))\n            if (providers.All(p => p.Identifier != providerGuid))\n            {\n                Trace.WriteLine(\"No provider with the identifier \" + providerGuid + \" is registered\");\n                return null;\n            }\n\n            // Get an instance.\n            var record = providers.FirstOrDefault(p => p.Identifier == providerGuid);\n\n            if (record != null)\n            {\n                Debug.Assert(record.Implementer != null, \"Need to know the implementing type for the provider\");\n\n                try\n                {\n                    var provider = (ILogProvider)Activator.CreateInstance(record.Implementer, settings);\n                    providerInstances.Add(new KeyValuePair<string, ILogProvider>(settings.Name, provider));\n                    return provider;\n                }\n                catch (Exception e)\n                {\n                    Trace.TraceError(e.ToString());\n                    Debugger.Break();\n                }\n            }\n\n            return null;\n        }\n\n        public ILogProvider Get(string name)\n        {\n            Debug.Assert(providerInstances.Any(p => p.Key == name), \"There is no instance with the identifier \" + name);\n            if (providerInstances.All(p => p.Key != name))\n            {\n                throw new ArgumentException(\"There is no instance with the identifier \" + name, nameof(name));\n            }\n\n            return providerInstances.FirstOrDefault(p => p.Key == name).Value;\n        }\n\n        public void Remove(string name)\n        {\n            throw new NotSupportedException(\"Removal is not yet supported\");\n        }\n\n        public IProviderInfo GetInformation(Guid providerGuid)\n        {\n            Debug.Assert(providers.Any(p => p.Identifier == providerGuid), \"No such registered Provider\");\n            if (providers.All(p => p.Identifier != providerGuid))\n            {\n                throw new ArgumentException(\n                    \"Specified guid does not correspond to a registered provider\",\n                    nameof(providerGuid));\n            }\n\n            return providers.First(p => p.Identifier == providerGuid).Info;\n        }\n\n        /// <summary>\n        /// Gets the configuration abstraction for the specified Guid.\n        /// Type left to caller to determine (as long as it is satisfied\n        /// by the implementer too).\n        /// </summary>\n        /// <typeparam name=\"T\">Type of configuration.</typeparam>\n        /// <param name=\"providerGuid\">Identifier of provider.</param>\n        /// <returns>Returns the provider associated to the supplied <see cref=\"Guid\"/>>.</returns>\n        public T GetConfiguration<T>(Guid providerGuid)\n        {\n            var matchesGuid = providers.Where(p => p.Identifier == providerGuid).Where(p => p.Settings != null);\n\n            // Simple checking for duplications.  At the moment, throw an\n            // exception if it happens - in the future, it might change to\n            // a last-registered wins policy (or maybe first wins!)\n            var matchesType = matchesGuid.Count(p => p.Settings.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(T))));\n\n            if (matchesType > 1)\n            {\n                throw new NotSupportedException(\n                    $\"There should only be one registered {typeof(T)} handler for the provider {providerGuid}\");\n            }\n\n            var matches =\n                matchesGuid.Where(p => p.Settings.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(T)))).Select(\n                    p => p.Settings);\n\n            var match = matches.LastOrDefault();\n            if (match != null)\n            {\n                return (T)Activator.CreateInstance(match);\n            }\n\n            return default(T);\n        }\n\n        public IEnumerator<Guid> GetEnumerator()\n        {\n            return providers.Select(p => p.Identifier).GetEnumerator();\n        }\n\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            return GetEnumerator();\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Providers/ProviderRegistrationRecord.cs",
    "content": "﻿namespace Sentinel.Providers\r\n{\r\n    using System;\r\n\r\n    using Sentinel.Interfaces.Providers;\r\n\r\n    public class ProviderRegistrationRecord : IProviderRegistrationRecord\r\n    {\r\n        public Guid Identifier { get; set; }\r\n\r\n        public IProviderInfo Info { get; set; }\r\n\r\n        public Type Settings { get; set; }\r\n\r\n        public Type Implementer { get; set; }\r\n    }\r\n}\r\n"
  },
  {
    "path": "Sentinel/Providers/ProviderSettings.cs",
    "content": "﻿namespace Sentinel.Providers\n{\n    using Sentinel.Interfaces.Providers;\n\n    public class ProviderSettings : IProviderSettings\n    {\n        public string Name { get; set; }\n\n        public virtual string Summary => $\"Provider named {Name}\";\n\n        /// <summary>\n        /// Gets or sets reference back to the provider this setting is appropriate to.\n        /// </summary>\n        public IProviderInfo Info { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Providers/SelectProviderPage.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Providers.SelectProviderPage\"\r\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\r\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\r\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \r\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \r\n             mc:Ignorable=\"d\" \r\n             d:DesignHeight=\"126\" d:DesignWidth=\"294\"\r\n             Loaded=\"PageLoaded\"\r\n             SnapsToDevicePixels=\"True\">\r\n    <Grid Margin=\"3\">\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition Height=\"Auto\" />\r\n            <RowDefinition Height=\"*\" />\r\n            <RowDefinition Height=\"Auto\" />\r\n        </Grid.RowDefinitions>\r\n        \r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition Width=\"Auto\" />\r\n            <ColumnDefinition Width=\"*\" />\r\n        </Grid.ColumnDefinitions>\r\n\r\n        <TextBlock Margin=\"3\"\r\n                   Text=\"Name : \"\r\n                   VerticalAlignment=\"Center\" />\r\n        <TextBox Margin=\"3\"\r\n                 Text=\"{Binding LoggerName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}\"\r\n                 Grid.Row=\"0\"\r\n                 Grid.Column=\"1\"\r\n                 VerticalAlignment=\"Center\" />\r\n        <TextBlock Text=\"Provider Type : \"\r\n                   Margin=\"3\"\r\n                   Grid.Row=\"1\"\r\n                   VerticalAlignment=\"Top\"/>\r\n        <ListView SelectionMode=\"Single\"\r\n                  ItemsSource=\"{Binding Providers}\"\r\n                  SelectedIndex=\"{Binding SelectedProvider}\"\r\n                  Grid.Row=\"1\"\r\n                  Grid.Column=\"1\" />\r\n        <TextBlock Text=\"Provider Details : \"\r\n                   Margin=\"3\"\r\n                   Grid.Row=\"2\"\r\n                   VerticalAlignment=\"Top\" />\r\n        <TextBlock Text=\"{Binding SelectedProviderDescription}\"\r\n                   Margin=\"3\"\r\n                   Grid.Row=\"2\"\r\n                   Grid.Column=\"1\"\r\n                   VerticalAlignment=\"Top\"/>\r\n\r\n    </Grid>\r\n</UserControl>\r\n"
  },
  {
    "path": "Sentinel/Providers/SelectProviderPage.xaml.cs",
    "content": "﻿namespace Sentinel.Providers\n{\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n\n    using log4net;\n\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.Providers.Interfaces;\n    using Sentinel.Services;\n\n    using WpfExtras;\n\n    /// <summary>\n    /// Interaction logic for SelectProviderPage.xaml.\n    /// </summary>\n    public partial class SelectProviderPage : IWizardPage\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(SelectProviderPage));\n\n        private readonly ObservableCollection<IWizardPage> children = new ObservableCollection<IWizardPage>();\n\n        private readonly List<IProviderInfo> providers = new List<IProviderInfo>();\n\n        private readonly IProviderManager providerManager;\n\n        /// <summary>\n        /// The additionalPages collection will maintain any child pages created\n        /// based upon the providers selection.  The indexes will match that of\n        /// the providers collection.  This is purely being done so that if a setting\n        /// is made on the child page, the provider changed (by going back) and then\n        /// reverted, the original settings will still be there.\n        /// </summary>\n        private readonly List<IWizardPage> additionalPages = new List<IWizardPage>();\n\n        private string name;\n\n        private bool isValid;\n\n        private int selectedProvider = -1;\n\n        private string selectedProviderDescription;\n\n        public SelectProviderPage()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            Children = new ReadOnlyObservableCollection<IWizardPage>(children);\n\n            // Register to self so that we can handler user interactions.\n            PropertyChanged += PropertyChangedHandler;\n\n            providerManager = ServiceLocator.Instance.Get<IProviderManager>();\n            if (providerManager != null)\n            {\n                foreach (var guid in providerManager)\n                {\n                    providers.Add(providerManager.GetInformation(guid));\n\n                    // If any additional page, we shall cache them with the\n                    // same index, so make sure the collection matches providers.\n                    additionalPages.Add(null);\n                }\n            }\n\n            LoggerName = \"Untitled\";\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public IEnumerable<string> Providers\n        {\n            get\n            {\n                return providers.Select(i => i.Name);\n            }\n        }\n\n        public int SelectedProvider\n        {\n            get\n            {\n                return selectedProvider;\n            }\n\n            set\n            {\n                if (selectedProvider != value)\n                {\n                    Log.DebugFormat(\"Selected provider index changed to {0}\", value);\n\n                    selectedProvider = value;\n                    OnPropertyChanged(nameof(SelectedProvider));\n                }\n            }\n        }\n\n        public string Title\n        {\n            get\n            {\n                return \"Select Provider\";\n            }\n        }\n\n        public string LoggerName\n        {\n            get\n            {\n                return name;\n            }\n\n            set\n            {\n                if (name != value)\n                {\n                    name = value;\n                    OnPropertyChanged(nameof(LoggerName));\n                }\n            }\n        }\n\n        public string Description => \"Select a log provider from the registered providers\";\n\n        public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        public string SelectedProviderDescription\n        {\n            get\n            {\n                Log.DebugFormat(\"Retrieving description: {0}\", selectedProviderDescription);\n                return selectedProviderDescription;\n            }\n\n            private set\n            {\n                if (selectedProviderDescription != value)\n                {\n                    selectedProviderDescription = value;\n                    OnPropertyChanged(nameof(SelectedProviderDescription));\n                }\n            }\n        }\n\n        public bool IsValid\n        {\n            get\n            {\n                return isValid;\n            }\n\n            private set\n            {\n                if (isValid != value)\n                {\n                    isValid = value;\n                    OnPropertyChanged(nameof(IsValid));\n                }\n            }\n        }\n\n        public Control PageContent => this;\n\n        public void AddChild(IWizardPage newItem)\n        {\n            children.Add(newItem);\n        }\n\n        public void RemoveChild(IWizardPage item)\n        {\n            children.Remove(item);\n        }\n\n        public object Save(object saveData)\n        {\n            if (!(saveData is ProviderSettings))\n            {\n                saveData = new ProviderSettings();\n            }\n\n            ((ProviderSettings)saveData).Info = providers[SelectedProvider];\n            ((ProviderSettings)saveData).Name = name;\n\n            return saveData;\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void SetChildPages(int index)\n        {\n            if (index < 0 || index >= providers.Count)\n            {\n                return;\n            }\n\n            SelectedProviderDescription = providers[index].Description;\n\n            // See whether a page can be cached.\n            if (additionalPages[index] == null)\n            {\n                var info = providers[index];\n\n                if (providerManager != null)\n                {\n                    additionalPages[index] = providerManager.GetConfiguration<IWizardPage>(info.Identifier);\n                }\n            }\n\n            while (children.Any())\n            {\n                var p = children.First();\n                RemoveChild(p);\n            }\n\n            if (additionalPages[index] != null)\n            {\n                AddChild(additionalPages[index]);\n            }\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"SelectedProvider\")\n            {\n                var index = SelectedProvider;\n                IsValid = index != -1 && !string.IsNullOrEmpty(name);\n                SetChildPages(index);\n            }\n        }\n\n        private void PageLoaded(object sender, RoutedEventArgs e)\n        {\n            if (SelectedProvider == -1 && providers.Any())\n            {\n                SelectedProvider = 0;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Sentinel.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\r\n\r\n  <PropertyGroup>\r\n    <OutputType>WinExe</OutputType>\r\n    <TargetFramework>net5.0-windows</TargetFramework>\r\n    <UseWPF>true</UseWPF>\r\n    <AssemblyName>Sentinel</AssemblyName>\r\n  </PropertyGroup>\r\n\r\n  <ItemGroup>\r\n    <Page Remove=\"MainApplication.xaml\" />\r\n    <ApplicationDefinition Include=\"MainApplication.xaml\" />\r\n  </ItemGroup>\r\n\r\n  <ItemGroup>\r\n    <PackageReference Include=\"CommandLineParser\" Version=\"2.9.1\" />\r\n    <PackageReference Include=\"log4net\" Version=\"2.0.15\" />\r\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.1\" />\r\n    <PackageReference Include=\"NodaTime\" Version=\"3.1.2\" />\r\n  </ItemGroup>\r\n\r\n  <ItemGroup>\r\n    <Resource Include=\"Resources\\Glyphs\\SortDownArrow.png\" />\r\n    <Resource Include=\"Resources\\Glyphs\\SortUpArrow.png\" />\r\n    <Resource Include=\"Resources\\Large\\Add.png\" />\r\n    <Resource Include=\"Resources\\Large\\Context.png\" />\r\n    <Resource Include=\"Resources\\Large\\Debug.png\" />\r\n    <Resource Include=\"Resources\\Large\\DebugSource.png\" />\r\n    <Resource Include=\"Resources\\Large\\Error.png\" />\r\n    <Resource Include=\"Resources\\Large\\Exception.png\" />\r\n    <Resource Include=\"Resources\\Large\\Exit.png\" />\r\n    <Resource Include=\"Resources\\Large\\Export.png\" />\r\n    <Resource Include=\"Resources\\Large\\Fatal.png\" />\r\n    <Resource Include=\"Resources\\Large\\Info.png\" />\r\n    <Resource Include=\"Resources\\Large\\Network.png\" />\r\n    <Resource Include=\"Resources\\Large\\Open.png\" />\r\n    <Resource Include=\"Resources\\Large\\Save.png\" />\r\n    <Resource Include=\"Resources\\Large\\Settings.png\" />\r\n    <Resource Include=\"Resources\\Large\\Thread.png\" />\r\n    <Resource Include=\"Resources\\Large\\Trace.png\" />\r\n    <Resource Include=\"Resources\\Large\\Unknown.png\" />\r\n    <Resource Include=\"Resources\\Large\\Warning.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Add.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Debug.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Error.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Exception.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Exit.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Export.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Fatal.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Info.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Network.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Open.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Save.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Settings.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Thread.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Trace.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Unknown.png\" />\r\n    <Resource Include=\"Resources\\Medium\\Warning.png\" />\r\n    <Resource Include=\"Resources\\Small\\Add.png\" />\r\n    <Resource Include=\"Resources\\Small\\Clear.png\" />\r\n    <Resource Include=\"Resources\\Small\\Clock.png\" />\r\n    <Resource Include=\"Resources\\Small\\Context.png\" />\r\n    <Resource Include=\"Resources\\Small\\Debug.png\" />\r\n    <Resource Include=\"Resources\\Small\\DebugSource.png\" />\r\n    <Resource Include=\"Resources\\Small\\Error.png\" />\r\n    <Resource Include=\"Resources\\Small\\Exception.png\" />\r\n    <Resource Include=\"Resources\\Small\\Exit.png\" />\r\n    <Resource Include=\"Resources\\Small\\Export.png\" />\r\n    <Resource Include=\"Resources\\Small\\Fatal.png\" />\r\n    <Resource Include=\"Resources\\Small\\Info.png\" />\r\n    <Resource Include=\"Resources\\Small\\Layout.png\" />\r\n    <Resource Include=\"Resources\\Small\\MonoLightning.png\" />\r\n    <Resource Include=\"Resources\\Small\\Network.png\" />\r\n    <Resource Include=\"Resources\\Small\\Open.png\" />\r\n    <Resource Include=\"Resources\\Small\\Pause.png\" />\r\n    <Resource Include=\"Resources\\Small\\Save.png\" />\r\n    <Resource Include=\"Resources\\Small\\ScrollDown.png\" />\r\n    <Resource Include=\"Resources\\Small\\Settings.png\" />\r\n    <Resource Include=\"Resources\\Small\\Thread.png\" />\r\n    <Resource Include=\"Resources\\Small\\Trace.png\" />\r\n    <Resource Include=\"Resources\\Small\\Unknown.png\" />\r\n    <Resource Include=\"Resources\\Small\\Warning.png\" />\r\n  </ItemGroup>\r\n</Project>\r\n"
  },
  {
    "path": "Sentinel/Services/AttributeHelper.cs",
    "content": "namespace Sentinel.Services\r\n{\r\n    using System;\r\n    using System.Linq;\r\n\r\n    using Sentinel.Interfaces;\r\n    using Sentinel.Interfaces.CodeContracts;\r\n\r\n    public static class AttributeHelper\r\n    {\r\n        public static bool HasAttribute<T>(this Type type)\r\n        {\r\n            type.ThrowIfNull(nameof(type));\r\n\r\n            var attributes = type.GetCustomAttributes(typeof(T), true);\r\n\r\n            return attributes.Any();\r\n        }\r\n\r\n        public static bool HasAttribute<T>(this object source)\r\n        {\r\n            source.ThrowIfNull(nameof(source));\r\n\r\n            return source.GetType().HasAttribute<T>();\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Services/DictionaryHelper.cs",
    "content": "namespace Sentinel.Services\n{\n    using System;\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public static class DictionaryHelper\n    {\n        /// <summary>\n        /// Safely returns the value from the dictionary associated to the key supplied, or the default (usually a null) for the\n        /// value type.\n        /// </summary>\n        /// <typeparam name=\"TKey\">Dictionary key type, can usually be deduced from the dictionary parameter.</typeparam>\n        /// <typeparam name=\"TValue\">Dictionary value type, can usually be deduced from the dictionary> parameter.</typeparam>\n        /// <param name=\"dictionary\">Dictionary in which to look.</param>\n        /// <param name=\"key\">Key to find in dictionary.</param>\n        /// <returns>Value corresponding to the supplied key.</returns>\n        public static TValue Get<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)\n        {\n            dictionary.ThrowIfNull(nameof(dictionary));\n            return dictionary.ContainsKey(key) ? dictionary[key] : default(TValue);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Services/Interfaces/ISessionManager.cs",
    "content": "﻿namespace Sentinel.Services.Interfaces\n{\n    using System.Collections.Generic;\n    using System.Runtime.Serialization;\n    using System.Windows;\n\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.Providers.Interfaces;\n\n    using WpfExtras;\n\n    public interface ISessionManager\n    {\n        /// <summary>\n        /// Gets the name of the session.\n        /// </summary>\n        [DataMember]\n        string Name { get; }\n\n        [DataMember]\n        IEnumerable<IProviderSettings> ProviderSettings { get; }\n\n        IEnumerable<ViewModelBase> ChangingViewModelBases { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the current session has been saved\n        /// in its current state (e.g. true if not dirty).\n        /// </summary>\n        bool IsSaved { get; set; }\n\n        /// <summary>\n        /// Called when loading a brand new session.\n        /// </summary>\n        /// <param name=\"parent\">The <see cref=\"Window\"/> to associate the new session.</param>\n        void LoadNewSession(Window parent);\n\n        /// <summary>\n        /// Initialise the session manager from supplied providers.\n        /// </summary>\n        /// <param name=\"providers\">Providers collection to load.</param>\n        void LoadProviders(IEnumerable<PendingProviderRecord> providers);\n\n        /// <summary>\n        /// Called when loading a session that has been saved to disk.\n        /// </summary>\n        /// <param name=\"filePath\">Directory where the session is saved.</param>\n        void LoadSession(string filePath);\n\n        /// <summary>\n        /// Save the current session to the specified path.\n        /// </summary>\n        /// <param name=\"filePath\">Directory to write sesion file.</param>\n        void SaveSession(string filePath);\n    }\n}\n"
  },
  {
    "path": "Sentinel/Services/ServiceLocator.cs",
    "content": "﻿namespace Sentinel.Services\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.Diagnostics;\n    using System.Diagnostics.CodeAnalysis;\n    using System.IO;\n    using System.Linq;\n    using System.Windows;\n\n    using log4net;\n\n    using Sentinel.Interfaces;\n\n    public class ServiceLocator\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(ServiceLocator));\n\n        private readonly Dictionary<Type, object> services = new Dictionary<Type, object>();\n\n        private ServiceLocator()\n        {\n            var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);\n            Log.DebugFormat(\"App Data folder {0}\", appData);\n\n            SaveLocation = Path.Combine(appData, \"Sentinel\");\n            Log.DebugFormat(\"Save location for internal files: {0}\", SaveLocation);\n\n            // Check the folder exists, otherwise create it\n            var di = new DirectoryInfo(SaveLocation);\n            if (!di.Exists)\n            {\n                Log.DebugFormat(\"Creating folder {0}\", SaveLocation);\n                try\n                {\n                    di.Create();\n                }\n                catch (Exception e)\n                {\n                    Log.Error(\"Unable to create directory\", e);\n                }\n            }\n        }\n\n        public static ServiceLocator Instance { get; } = new ServiceLocator();\n\n        public string SaveLocation { get; private set; }\n\n        public ReadOnlyCollection<object> RegisteredServices\n        {\n            get\n            {\n                Debug.Assert(services.Values != null, \"Values collection should always exist\");\n                return new ReadOnlyCollection<object>(services.Values.ToList());\n            }\n        }\n\n        public bool ReportErrors { get; set; }\n\n        [SuppressMessage(\n            \"Microsoft.Design\",\n            \"CA1004:GenericMethodsShouldProvideTypeParameter\",\n            Justification = \"This approach has been chosen as the intended usage style.\")]\n        public T Get<T>()\n        {\n            if (services.ContainsKey(typeof(T)))\n            {\n                return (T)services[typeof(T)];\n            }\n\n            if (ReportErrors)\n            {\n                var errorMessage = $\"No registered service supporting {typeof(T)}\";\n                Log.Error(errorMessage);\n                MessageBox.Show(errorMessage, \"Service location error\", MessageBoxButton.OK, MessageBoxImage.Error);\n            }\n\n            return default(T);\n        }\n\n        [SuppressMessage(\n            \"Microsoft.Design\",\n            \"CA1004:GenericMethodsShouldProvideTypeParameter\",\n            Justification = \"The generic style registration is desired, despite this rule.\")]\n        public bool IsRegistered<T>()\n        {\n            return services.Keys.Contains(typeof(T));\n        }\n\n        [SuppressMessage(\n            \"Microsoft.Design\",\n            \"CA1004:GenericMethodsShouldProvideTypeParameter\",\n            Justification = \"The generic style registration is desired, despite this rule.\")]\n        public void Register<T>(object serviceInstance)\n        {\n            services[typeof(T)] = serviceInstance;\n        }\n\n        public void Register(Type keyType, Type instanceType, bool replace)\n        {\n            Log.DebugFormat(\n                \"Registering Type instance of '{0}' to signature of '{1}'\",\n                instanceType.Name,\n                keyType.Name);\n\n            if (!services.Keys.Contains(keyType) || replace)\n            {\n                services[keyType] = Activator.CreateInstance(instanceType);\n\n                var defaultInitialisation = services[keyType] as IDefaultInitialisation;\n                defaultInitialisation?.Initialise();\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Services/SessionManager.cs",
    "content": "﻿namespace Sentinel.Services\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Diagnostics;\n    using System.IO;\n    using System.Linq;\n    using System.Runtime.Serialization;\n    using System.Text;\n    using System.Windows;\n\n    using Newtonsoft.Json.Linq;\n\n    using Sentinel.Classification;\n    using Sentinel.Classification.Interfaces;\n    using Sentinel.Extractors;\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.FileMonitor;\n    using Sentinel.Filters;\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Highlighters;\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Images;\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.Providers;\n    using Sentinel.Log4Net;\n    using Sentinel.Logger;\n    using Sentinel.Logs;\n    using Sentinel.Logs.Gui;\n    using Sentinel.Logs.Interfaces;\n    using Sentinel.NLog;\n    using Sentinel.Preferences;\n    using Sentinel.Providers;\n    using Sentinel.Providers.Interfaces;\n    using Sentinel.Services.Interfaces;\n    using Sentinel.Support;\n    using Sentinel.Views;\n    using Sentinel.Views.Gui;\n    using Sentinel.Views.Interfaces;\n\n    using WpfExtras;\n\n    [DataContract]\n    public class SessionManager : ISessionManager\n    {\n        private const char ObjectSeparator = '~';\n\n        private bool serviceLocatorIsFresh;\n\n        public SessionManager()\n        {\n            Name = \"Untitled\";\n            RefreshServiceLocator();\n        }\n\n        public IEnumerable<ViewModelBase> ChangingViewModelBases { get; set; }\n\n        public bool IsSaved { get; set; }\n\n        public string Name { get; private set; }\n\n        public IEnumerable<IProviderSettings> ProviderSettings\n        {\n            get\n            {\n                var providerManager = ServiceLocator.Instance.Get<IProviderManager>();\n                return providerManager.Instances.Select(c => c.ProviderSettings);\n            }\n        }\n\n        public void LoadNewSession(Window parent)\n        {\n            if (!serviceLocatorIsFresh)\n            {\n                CleanUpResources();\n                Name = \"Untitled\";\n                RefreshServiceLocator();\n            }\n\n            var wizard = new NewLoggerWizard();\n\n            if (!wizard.Display(parent))\n            {\n                return;\n            }\n\n            var settings = wizard.Settings;\n\n            // Set session properties\n            Name = settings.LogName;\n\n            ConfigureLoggerServices(settings.LogName, settings.Views, settings.Providers);\n\n            IsSaved = false;\n            serviceLocatorIsFresh = false;\n        }\n\n        public void LoadProviders(IEnumerable<PendingProviderRecord> providers)\n        {\n            CleanUpResources();\n\n            var views = new List<string> { ServiceLocator.Instance.Get<IViewManager>().Registered.First().Identifier };\n\n            ConfigureLoggerServices(\"Untitled\", views, providers);\n\n            IsSaved = false;\n        }\n\n        public void LoadSession(string fileName)\n        {\n            var fileText = File.ReadAllText(fileName);\n            var jsonObjects = fileText.Split(ObjectSeparator);\n\n            CleanUpResources();\n            LoadServiceLocator(jsonObjects);\n\n            IsSaved = false;\n        }\n\n        public void SaveSession(string filePath)\n        {\n            var stringToSave = new StringBuilder();\n            var services = ServiceLocator.Instance.RegisteredServices;\n            foreach (var value in services)\n            {\n                if (value == null)\n                {\n                    Trace.TraceError(\"Unexpected null\");\n                    continue;\n                }\n\n                if (value.HasAttribute<DataContractAttribute>())\n                {\n                    stringToSave.AppendLine(JsonHelper.SerializeToString(value));\n                    stringToSave.AppendLine(ObjectSeparator.ToString());\n                }\n            }\n\n            using (var fs = File.Create(filePath))\n            {\n                var info = new UTF8Encoding(true).GetBytes(stringToSave.ToString());\n                fs.Write(info, 0, info.Length);\n            }\n\n            IsSaved = true;\n        }\n\n        private static void ConfigureLoggerServices(\n            string logName,\n            IEnumerable<string> viewIdentifiers,\n            IEnumerable<PendingProviderRecord> pendingProviderRecords)\n        {\n            // Create the logger.\n            var logManager = ServiceLocator.Instance.Get<ILogManager>();\n            var log = logManager.Add(logName);\n\n            // Create the frame view\n            var viewManager = ServiceLocator.Instance.Get<IViewManager>();\n            Debug.Assert(\n                viewManager != null,\n                \"A ViewManager should be registered with service locator for the IViewManager interface\");\n\n            var frame = ServiceLocator.Instance.Get<IWindowFrame>();\n            frame.Log = log;\n            frame.SetViews(viewIdentifiers);\n            viewManager.Viewers.Add(frame);\n\n            // Create the providers.\n            var providerManager = ServiceLocator.Instance.Get<IProviderManager>();\n            foreach (var providerRecord in pendingProviderRecords)\n            {\n                var provider = providerManager.Create(providerRecord.Info.Identifier, providerRecord.Settings);\n                provider.Logger = log;\n                provider.Start();\n            }\n        }\n\n        private void CleanUpResources()\n        {\n            // Close all open providers\n            var providerManager = ServiceLocator.Instance.Get<IProviderManager>();\n            foreach (var provider in providerManager.Instances)\n            {\n                provider.Close();\n            }\n\n            // Unregister changing viewmodelbases\n            foreach (var viewmodel in ChangingViewModelBases)\n            {\n                viewmodel.PropertyChanged -= ViewModelProperty_Changed;\n            }\n        }\n\n        private void LoadChangingViewModelBases()\n        {\n            var viewModelBases = new List<ViewModelBase>();\n            var locator = ServiceLocator.Instance;\n            viewModelBases.Add((SearchExtractor)locator.Get<ISearchExtractor>());\n            viewModelBases.Add((SearchFilter)locator.Get<ISearchFilter>());\n            viewModelBases.Add((HighlightingService<IHighlighter>)locator.Get<IHighlightingService<IHighlighter>>());\n            viewModelBases.Add((ExtractingService<IExtractor>)locator.Get<IExtractingService<IExtractor>>());\n            viewModelBases.Add((FilteringService<IFilter>)locator.Get<IFilteringService<IFilter>>());\n            viewModelBases.Add((ClassifyingService<IClassifier>)locator.Get<IClassifyingService<IClassifier>>());\n\n            ChangingViewModelBases = viewModelBases;\n\n            foreach (var item in ChangingViewModelBases)\n            {\n                item.PropertyChanged += ViewModelProperty_Changed;\n            }\n        }\n\n        private void LoadServiceLocator(IEnumerable<string> jsonObjectStrings)\n        {\n            if (jsonObjectStrings == null)\n            {\n                return;\n            }\n\n            var locator = ServiceLocator.Instance;\n            var pendingProviderRecords = new List<PendingProviderRecord>();\n\n            foreach (var objString in jsonObjectStrings)\n            {\n                if (!string.IsNullOrWhiteSpace(objString))\n                {\n                    var deserializedObj = JObject.Parse(objString);\n                    var typeString = deserializedObj[\"$type\"].ToString();\n\n                    if (typeString.Contains(typeof(UserPreferences).ToString()))\n                    {\n                        locator.Register<IUserPreferences>(JsonHelper.DeserializeFromString<UserPreferences>(objString));\n                    }\n                    else if (typeString.Contains(typeof(SearchFilter).Name))\n                    {\n                        locator.Register<ISearchFilter>(JsonHelper.DeserializeFromString<SearchFilter>(objString));\n                    }\n                    else if (typeString.Contains(typeof(SearchExtractor).Name))\n                    {\n                        locator.Register<ISearchExtractor>(JsonHelper.DeserializeFromString<SearchExtractor>(objString));\n                    }\n                    else if (typeString.Contains(typeof(FilteringService<>).Name))\n                    {\n                        locator.Register<IFilteringService<IFilter>>(\n                            JsonHelper.DeserializeFromString<FilteringService<IFilter>>(objString));\n                    }\n                    else if (typeString.Contains(typeof(ExtractingService<>).Name))\n                    {\n                        locator.Register<IExtractingService<IExtractor>>(\n                            JsonHelper.DeserializeFromString<ExtractingService<IExtractor>>(objString));\n                    }\n                    else if (typeString.Contains(typeof(HighlightingService<>).Name))\n                    {\n                        locator.Register<IHighlightingService<IHighlighter>>(\n                            JsonHelper.DeserializeFromString<HighlightingService<IHighlighter>>(objString));\n                    }\n                    else if (typeString.Contains(typeof(SearchHighlighter).Name))\n                    {\n                        locator.Register<ISearchHighlighter>(\n                            JsonHelper.DeserializeFromString<SearchHighlighter>(objString));\n                    }\n                    else if (typeString.Contains(typeof(ClassifyingService<>).Name))\n                    {\n                        locator.Register<IClassifyingService<IClassifier>>(\n                            JsonHelper.DeserializeFromString<ClassifyingService<IClassifier>>(objString));\n                    }\n                    else if (typeString.Contains(typeof(TypeToImageService).Name))\n                    {\n                        locator.Register<ITypeImageService>(\n                            JsonHelper.DeserializeFromString<TypeToImageService>(objString));\n                    }\n                    else if (typeString.Contains(typeof(SessionManager).Name))\n                    {\n                        Name = deserializedObj[\"Name\"].ToString();\n\n                        LoadChangingViewModelBases();\n\n                        var providerSettingsObj = deserializedObj[\"ProviderSettings\"].HasValues\n                                                      ? deserializedObj[\"ProviderSettings\"].Values()\n                                                      : null;\n\n                        if (providerSettingsObj == null)\n                        {\n                            continue;\n                        }\n\n                        var providerInstances = providerSettingsObj.Last();\n                        foreach (var providerSetting in providerInstances)\n                        {\n                            var settings = providerSetting.ToString();\n\n                            var name = providerSetting[\"$type\"].ToString();\n                            if (name.Contains(typeof(NetworkSettings).Name))\n                            {\n                                var thisSetting = JsonHelper.DeserializeFromString<NetworkSettings>(settings);\n                                pendingProviderRecords.Add(new PendingProviderRecord\n                                                               {\n                                                                   Info = thisSetting.Info,\n                                                                   Settings = thisSetting,\n                                                               });\n                            }\n                            else if (name.Contains(typeof(UdpAppenderSettings).Name))\n                            {\n                                var thisSetting = JsonHelper.DeserializeFromString<UdpAppenderSettings>(settings);\n                                pendingProviderRecords.Add(new PendingProviderRecord\n                                                               {\n                                                                   Info = thisSetting.Info,\n                                                                   Settings = thisSetting,\n                                                               });\n                            }\n                            else if (name.Contains(typeof(FileMonitoringProviderSettings).Name))\n                            {\n                                var thisSetting =\n                                    JsonHelper.DeserializeFromString<FileMonitoringProviderSettings>(settings);\n                                pendingProviderRecords.Add(new PendingProviderRecord { Info = thisSetting.Info, Settings = thisSetting, });\n                            }\n                            else\n                            {\n                                Trace.TraceError($\"No PendingProviderRecord for type of {name}\");\n                            }\n                        }\n                    }\n                    else\n                    {\n                        Trace.TraceError($\"No deconstruction supplied for type {typeString}\");\n                    }\n                }\n            }\n\n            // Load new objects for the rest.\n            locator.Register<ILogManager>(new LogManager());\n            locator.Register<LogWriter>(new LogWriter());\n            locator.Register(typeof(IViewManager), typeof(ViewManager), false);\n            locator.Register<IProviderManager>(new ProviderManager());\n            locator.Register<IWindowFrame>(new MultipleViewFrame()); // needs IUserPreferences, IViewManager\n            locator.Register<ILogFileExporter>(new LogFileExporter());\n\n            locator.Register<INewProviderWizard>(new NewProviderWizard());\n\n            // Do this last so that other services have registered, e.g. the\n            // TypeImageService is called by some classifiers!\n            if (!locator.IsRegistered<IClassifyingService<IClassifier>>())\n            {\n                locator.Register(\n                    typeof(IClassifyingService<IClassifier>),\n                    typeof(ClassifyingService<IClassifier>),\n                    true);\n            }\n\n            var viewIDs = new List<string> { locator.Get<IViewManager>().Registered.First().Identifier };\n\n            ConfigureLoggerServices(Name, viewIDs, pendingProviderRecords);\n\n            GC.Collect();\n\n            serviceLocatorIsFresh = false;\n        }\n\n        private void RefreshServiceLocator()\n        {\n            var locator = ServiceLocator.Instance;\n\n            locator.Register(typeof(IUserPreferences), typeof(UserPreferences), true);\n            locator.Register(typeof(ISearchFilter), typeof(SearchFilter), true);\n            locator.Register(typeof(ISearchExtractor), typeof(SearchExtractor), true);\n            locator.Register(typeof(IFilteringService<IFilter>), typeof(FilteringService<IFilter>), true);\n            locator.Register(typeof(IExtractingService<IExtractor>), typeof(ExtractingService<IExtractor>), true);\n            locator.Register(\n                typeof(IHighlightingService<IHighlighter>),\n                typeof(HighlightingService<IHighlighter>),\n                true);\n            locator.Register(typeof(ISearchHighlighter), typeof(SearchHighlighter), true);\n            locator.Register(typeof(IClassifyingService<IClassifier>), typeof(ClassifyingService<IClassifier>), true);\n\n            locator.Register(typeof(ITypeImageService), typeof(TypeToImageService), true);\n            locator.Register<ILogManager>(new LogManager());\n            locator.Register<LogWriter>(new LogWriter());\n            locator.Register(typeof(IViewManager), typeof(ViewManager), false);\n            locator.Register<IProviderManager>(new ProviderManager());\n            locator.Register<IWindowFrame>(new MultipleViewFrame()); // needs IUserPreferences, IViewManager\n            locator.Register<ILogFileExporter>(new LogFileExporter());\n\n            locator.Register<INewProviderWizard>(new NewProviderWizard());\n\n            LoadChangingViewModelBases();\n\n            GC.Collect(); // collect all things without a reference\n\n            serviceLocatorIsFresh = true;\n        }\n\n        private void ViewModelProperty_Changed(object sender, System.ComponentModel.PropertyChangedEventArgs e)\n        {\n            IsSaved = false;\n            serviceLocatorIsFresh = false;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/StartUp/IOptions.cs",
    "content": "﻿namespace Sentinel.StartUp\n{\n    using CommandLine;\n\n    public interface IOptions\n    {\n        [Option('p', \"port\")]\n        int Port { get; set; }\n\n        [Option('u', \"udp\", SetName = \"protocols\")]\n        bool IsUdp { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/StartUp/Log4NetOptions.cs",
    "content": "namespace Sentinel.StartUp\n{\n    using CommandLine;\n\n    [Verb(\"log4net\", HelpText = \"Use log4net listener\")]\n    public class Log4NetOptions : IOptions\n    {\n        public Log4NetOptions()\n        {\n            IsUdp = true;\n            Port = 9998;\n        }\n\n        public int Port { get; set; }\n\n        public bool IsUdp { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/StartUp/NLogOptions.cs",
    "content": "﻿namespace Sentinel.StartUp\n{\n    using CommandLine;\n\n    [Verb(\"nlog\", HelpText = \"Use nlog listener\")]\n    public class NLogOptions : IOptions\n    {\n        public NLogOptions()\n        {\n            IsUdp = true;\n            Port = 9999;\n        }\n\n        [Option('t', \"tcp\", SetName = \"protocols\")]\n        public bool IsTcp\n        {\n            get => !IsUdp;\n            set => IsUdp = !value;\n        }\n\n        public int Port { get; set; }\n\n        public bool IsUdp { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/BooleanToWidthConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Diagnostics;\n    using System.Globalization;\n    using System.Windows.Data;\n\n    public class BooleanToWidthConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            Debug.Assert(value is bool, \"Value must be a boolean.\");\n            Debug.Assert(parameter is string, \"Parameter must be a string.\");\n            return (bool)value ? double.Parse(parameter.ToString()) : 0.0;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/CollapseIfNullConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows;\n    using System.Windows.Data;\n\n    public class CollapseIfNullConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var s = value as string;\n            if (string.IsNullOrWhiteSpace(s))\n            {\n                value = null;\n            }\n\n            return value == null\n                       ? (parameter == null ? Visibility.Collapsed : Visibility.Visible)\n                       : (parameter == null ? Visibility.Visible : Visibility.Collapsed);\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/DatePreferenceConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Globalization;\n    using System.Linq;\n    using System.Windows.Data;\n\n    using log4net;\n\n    using NodaTime;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public class DatePreferenceConverter : IValueConverter\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(DatePreferenceConverter));\n\n        private IUserPreferences Preferences { get; set; }\n\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            Preferences = parameter as IUserPreferences;\n\n            if (Preferences == null)\n            {\n                Log.Error(\"Parameter must be an IUserPreferences\");\n                throw new ArgumentException(\"Parameter must be an instance of IUserPreferences\", nameof(parameter));\n            }\n\n            var message = value as ILogEntry;\n\n            if (message == null)\n            {\n                Log.Warn(\"Not supplied an ILogEntry as the value parameter\");\n                return string.Empty;\n            }\n\n            object displayDateTime = null;\n            if (Preferences.UseArrivalDateTime)\n            {\n                message.MetaData.TryGetValue(\"ReceivedTime\", out displayDateTime);\n            }\n\n            // Fallback if message does not contain meta-data.\n            // TODO: safely handle the meta-data dateTime not being a date-time!\n            var dt = (DateTime?)displayDateTime ?? message.DateTime;\n\n            // TODO: make a time selection option....\n            if (dt.Kind == DateTimeKind.Utc && Preferences.ConvertUtcTimesToLocalTimeZone)\n            {\n                var defaultTimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault();\n                var global = new ZonedDateTime(Instant.FromDateTimeUtc(dt), defaultTimeZone);\n                return\n                    global.ToString(\n                        GetDateDisplayFormat(Preferences.SelectedDateOption, Preferences.DateFormatOptions),\n                        CultureInfo.CurrentCulture);\n            }\n\n            var local = LocalDateTime.FromDateTime(dt);\n            return local.ToString(\n                GetDateDisplayFormat(Preferences.SelectedDateOption, Preferences.DateFormatOptions),\n                CultureInfo.CurrentCulture);\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n\n        private static string GetDateDisplayFormat(int setting, IEnumerable<string> settings)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            var dateFormatSource = settings.ElementAt(setting);\n\n            // Need to quote special characters, this will only happen when changing formats, so don't need to be too clever.\n            return dateFormatSource.Replace(\"-\", \"'-'\").Replace(\":\", \"':'\");\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/ImageLibraryConverter.cs",
    "content": "﻿namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows.Data;\n\n    public class ImageLibraryConverter : IValueConverter\n    {\n        /// <summary>\n        ///   Converts a value.\n        /// </summary>\n        /// <returns>\n        ///   A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name = \"value\">The value produced by the binding source.</param>\n        /// <param name = \"targetType\">The type of the binding target property.</param>\n        /// <param name = \"parameter\">The converter parameter to use.</param>\n        /// <param name = \"culture\">The culture to use in the converter.</param>\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            return $\"/Resources/Small/{value}.png\";\n        }\n\n        /// <summary>\n        ///   Converts a value.\n        /// </summary>\n        /// <returns>\n        ///   A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name = \"value\">The value that is produced by the binding target.</param>\n        /// <param name = \"targetType\">The type to convert to.</param>\n        /// <param name = \"parameter\">The converter parameter to use.</param>\n        /// <param name = \"culture\">The culture to use in the converter.</param>\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/LongPathToShortPathConverter.cs",
    "content": "﻿namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.IO;\n    using System.Text;\n    using System.Windows.Data;\n\n    [ValueConversion(typeof(string), typeof(string))]\n    public class LongPathToShortPathConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var valueString = (string)value;\n            var pathParts = valueString.Split(Path.DirectorySeparatorChar);\n\n            if (pathParts.Length > 6)\n            {\n                var retData = new StringBuilder();\n                retData.Append(Path.Combine(pathParts[0], pathParts[1], pathParts[3]));\n                retData.Append(\"...\");\n                retData.Append(Path.Combine(pathParts[pathParts.Length - 2], pathParts[pathParts.Length - 1]));\n\n                return retData.ToString();\n            }\n            else\n            {\n                return valueString;\n            }\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "Sentinel/Support/Converters/MessageHasExceptionMetadataConverter.cs",
    "content": "namespace Sentinel.Support.Converters\r\n{\r\n    using System;\r\n    using System.Globalization;\r\n    using System.Windows.Data;\r\n    using Sentinel.Interfaces;\r\n\r\n    public class MessageHasExceptionMetadataConverter : IValueConverter\r\n    {\r\n        /// <summary>\r\n        /// Converts a value.\r\n        /// </summary>\r\n        /// <returns>\r\n        /// A converted value. If the method returns null, the valid null value is used.\r\n        /// </returns>\r\n        /// <param name=\"value\">The value produced by the binding source.</param>\r\n        /// <param name=\"targetType\">The type of the binding target property.</param>\r\n        /// <param name=\"parameter\">The converter parameter to use.</param>\r\n        /// <param name=\"culture\">The culture to use in the converter.</param>\r\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\r\n        {\r\n            object exception = null;\r\n            var entry = value as ILogEntry;\r\n            if (entry?.MetaData != null)\r\n            {\r\n                entry.MetaData.TryGetValue(\"Exception\", out exception);\r\n            }\r\n\r\n            return exception != null;\r\n        }\r\n\r\n        /// <summary>Converts a value.</summary>\r\n        /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>\r\n        /// <param name=\"value\">The value that is produced by the binding target.</param>\r\n        /// <param name=\"targetType\">The type to convert to.</param>\r\n        /// <param name=\"parameter\">The converter parameter to use.</param>\r\n        /// <param name=\"culture\">The culture to use in the converter.</param>\r\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\r\n        {\r\n            throw new NotImplementedException();\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Support/Converters/MetaDataConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Globalization;\n    using System.Windows.Data;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public class MetaDataConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            value.ThrowIfNull(nameof(value));\n\n            var member = parameter as string;\n\n            if (value is IDictionary<string, object> metaData && !string.IsNullOrWhiteSpace(member))\n            {\n                metaData.TryGetValue(member, out var metaDataValue);\n                return metaDataValue;\n            }\n\n            return string.Empty;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/MetaDataParameterConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Globalization;\n    using System.Windows.Data;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public class MetaDataParameterConverter : IMultiValueConverter\n    {\n        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)\n        {\n            values.ThrowIfNull(nameof(values));\n\n            if (values.Length != 2)\n            {\n                throw new ArgumentException(\"Must have two values\", nameof(values));\n            }\n\n            var meta = values[0] as IDictionary<string, object>;\n            if (meta == null)\n            {\n                throw new ArgumentException(\"First value in values needs to be IDictionary<string, object>\");\n            }\n\n            var member = values[1] as string;\n            if (member == null)\n            {\n                throw new ArgumentException(\"Second value in values[] must be a string\");\n            }\n\n            if (meta.TryGetValue(member, out var metaDataValue))\n            {\n                return metaDataValue;\n            }\n\n            return string.Empty;\n        }\n\n        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/TimePreferenceConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Globalization;\n    using System.Linq;\n    using System.Windows.Data;\n\n    using log4net;\n    \n    using NodaTime;\n    \n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public class TimePreferenceConverter : IValueConverter\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(TimePreferenceConverter));\n\n        private IUserPreferences Preferences { get; set; }\n\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            Preferences = parameter as IUserPreferences;\n\n            if (Preferences == null)\n            {\n                Log.Error(\"Parameter must be an IUserPreferences\");\n                throw new ArgumentException(\"Parameter must be an instance of IUserPreferences\", nameof(parameter));\n            }\n\n            if (!(value is ILogEntry))\n            {\n                Log.Warn(\"Not supplied an ILogEntry as the value parameter\");\n                return string.Empty;\n            }\n\n            object displayDateTime = null;\n            if (Preferences.UseArrivalDateTime)\n            {\n                (value as ILogEntry).MetaData.TryGetValue(\"ReceivedTime\", out displayDateTime);\n            }\n\n            // Fallback if message does not contain meta-data.\n            var dt = (DateTime)(displayDateTime ?? (value as ILogEntry).DateTime);\n            var isUtc = dt.Kind == DateTimeKind.Utc;\n            if (isUtc && Preferences.ConvertUtcTimesToLocalTimeZone)\n            {\n                var defaultTimeZone = DateTimeZoneProviders.Tzdb.GetSystemDefault();\n                var global = new ZonedDateTime(Instant.FromDateTimeUtc(dt), defaultTimeZone);\n                return\n                    global.ToString(\n                        GetDateDisplayFormat(Preferences.SelectedTimeFormatOption, Preferences.TimeFormatOptions, true),\n                        CultureInfo.CurrentCulture);\n            }\n\n            var local = LocalDateTime.FromDateTime(dt);\n            var time =\n                local.ToString(\n                    GetDateDisplayFormat(Preferences.SelectedTimeFormatOption, Preferences.TimeFormatOptions, false),\n                    CultureInfo.CurrentCulture);\n\n            return isUtc ? time + \" [UTC]\" : time;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n\n        private static string GetDateDisplayFormat(int setting, IEnumerable<string> settings, bool convertToLocalIfUtc)\n        {\n            settings.ThrowIfNull(nameof(settings));\n\n            var dateFormatSource = settings.ElementAt(setting);\n            var dateFormat = dateFormatSource.Replace(\"-\", \"'-'\").Replace(\":\", \"':'\");\n\n            // Need to quote special characters, this will only happen when changing formats, so don't need to be too clever.\n            return convertToLocalIfUtc ? dateFormat + \" x\" : dateFormat;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/TimeSinceDateTimeConverter.cs",
    "content": "﻿namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Diagnostics;\n    using System.Globalization;\n    using System.Windows.Data;\n\n    public class TimeSinceDateTimeConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            if (value != null)\n            {\n                DateTime dateTime;\n                if (!(value is DateTime))\n                {\n                    Trace.WriteLine($\"Time wasn't passed as DateTime, but as a {value.GetType()}\");\n                    dateTime = DateTime.Parse((string)value);\n                }\n                else\n                {\n                    dateTime = (DateTime)value;\n                }\n\n                // adjust the timezone information in the dateTime\n                DateTime adjusted = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);\n                TimeSpan elapsed = DateTimeOffset.UtcNow - adjusted;\n                return $\"{elapsed.Hours:D2}:{elapsed.Minutes:D2}:{elapsed.Seconds:D2},{elapsed.Milliseconds:D3}\";\n            }\n\n            return null;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/TypeToImageConverter.cs",
    "content": "namespace Sentinel.Support.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows.Data;\n    using System.Windows.Media;\n    using System.Windows.Media.Imaging;\n    using Sentinel.Images;\n    using Sentinel.Images.Interfaces;\n    using Sentinel.Services;\n\n    [ValueConversion(typeof(string), typeof(ImageSource))]\n    public class TypeToImageConverter : IValueConverter\n    {\n        protected ImageQuality Quality { get; set; } = ImageQuality.BestAvailable;\n\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var imageService = ServiceLocator.Instance.Get<ITypeImageService>();\n            var valueAsString = value as string;\n\n            if (!string.IsNullOrWhiteSpace(valueAsString))\n            {\n                var imageOptions = new ImageOptions\n                {\n                    Quality = Quality,\n                    AcceptLowerQuality = true,\n                    ImageMustExist = true,\n                };\n\n                var record = imageService?.Get(valueAsString, imageOptions);\n\n                if (!string.IsNullOrEmpty(record?.Image))\n                {\n                    var image = new BitmapImage();\n\n                    image.BeginInit();\n                    image.UriSource = new Uri(record.Image, UriKind.RelativeOrAbsolute);\n                    image.EndInit();\n\n                    return image;\n                }\n            }\n\n            return null;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Converters/TypeToLargeImageConverter.cs",
    "content": "namespace Sentinel.Support.Converters\r\n{\r\n    using Sentinel.Images;\r\n\r\n    public class TypeToLargeImageConverter : TypeToImageConverter\r\n    {\r\n        public TypeToLargeImageConverter()\r\n        {\r\n            Quality = ImageQuality.Large;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Support/Converters/TypeToMediumImageConverter.cs",
    "content": "namespace Sentinel.Support.Converters\r\n{\r\n    using Sentinel.Images;\r\n\r\n    public class TypeToMediumImageConverter : TypeToImageConverter\r\n    {\r\n        public TypeToMediumImageConverter()\r\n        {\r\n            Quality = ImageQuality.Medium;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Support/Converters/TypeToSmallImageConverter.cs",
    "content": "namespace Sentinel.Support.Converters\r\n{\r\n    using Sentinel.Images;\r\n\r\n    public class TypeToSmallImageConverter : TypeToImageConverter\r\n    {\r\n        public TypeToSmallImageConverter()\r\n        {\r\n            Quality = ImageQuality.Small;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "Sentinel/Support/GridViewSort.cs",
    "content": "﻿namespace Sentinel.Support\n{\n    using System;\n    using System.ComponentModel;\n    using System.Diagnostics.CodeAnalysis;\n    using System.Diagnostics.Contracts;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Controls.Primitives;\n    using System.Windows.Documents;\n    using System.Windows.Input;\n    using System.Windows.Media;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    public static class GridViewSort\n    {\n        public static readonly DependencyProperty AutoSortProperty;\n\n        public static readonly DependencyProperty CommandProperty;\n\n        public static readonly DependencyProperty PropertyNameProperty =\n            DependencyProperty.RegisterAttached(\n                \"PropertyName\",\n                typeof(string),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(null));\n\n        public static readonly DependencyProperty ShowSortGlyphProperty =\n            DependencyProperty.RegisterAttached(\n                \"ShowSortGlyph\",\n                typeof(bool),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(true));\n\n        public static readonly DependencyProperty SortGlyphAscendingProperty =\n            DependencyProperty.RegisterAttached(\n                \"SortGlyphAscending\",\n                typeof(ImageSource),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(null));\n\n        public static readonly DependencyProperty SortGlyphDescendingProperty =\n            DependencyProperty.RegisterAttached(\n                \"SortGlyphDescending\",\n                typeof(ImageSource),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(null));\n\n        private static readonly DependencyProperty SortedColumnHeaderProperty =\n            DependencyProperty.RegisterAttached(\n                \"SortedColumnHeader\",\n                typeof(GridViewColumnHeader),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(null));\n\n        static GridViewSort()\n        {\n            PropertyChangedCallback commandCallback = (o, e) =>\n                {\n                    ItemsControl listView = o as ItemsControl;\n                    if (listView != null)\n                    {\n                        if (!GetAutoSort(listView))\n                        {\n                            if (e.OldValue != null && e.NewValue == null)\n                            {\n                                listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeaderClick));\n                            }\n\n                            if (e.OldValue == null && e.NewValue != null)\n                            {\n                                listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeaderClick));\n                            }\n                        }\n                    }\n                };\n\n            CommandProperty = DependencyProperty.RegisterAttached(\n                \"Command\",\n                typeof(ICommand),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(null, commandCallback));\n\n            PropertyChangedCallback autoSortCallback = (o, e) =>\n                {\n                    ListView listView = o as ListView;\n                    if (listView != null)\n                    {\n                        // Don't change click handler if a command is set\n                        if (GetCommand(listView) == null)\n                        {\n                            bool oldValue = (bool)e.OldValue;\n                            bool newValue = (bool)e.NewValue;\n                            if (oldValue && !newValue)\n                            {\n                                listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeaderClick));\n                            }\n\n                            if (!oldValue && newValue)\n                            {\n                                listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeaderClick));\n                            }\n                        }\n                    }\n                };\n\n            AutoSortProperty = DependencyProperty.RegisterAttached(\n                \"AutoSort\",\n                typeof(bool),\n                typeof(GridViewSort),\n                new UIPropertyMetadata(false, autoSortCallback));\n        }\n\n        public static void ApplySort(\n            ICollectionView view,\n            string propertyName,\n            ListView listView,\n            GridViewColumnHeader sortedColumnHeader)\n        {\n            view.ThrowIfNull(nameof(view));\n\n            var direction = ListSortDirection.Ascending;\n            if (view.SortDescriptions.Count > 0)\n            {\n                SortDescription currentSort = view.SortDescriptions[0];\n                if (currentSort.PropertyName == propertyName)\n                {\n                    direction = currentSort.Direction == ListSortDirection.Ascending\n                                    ? ListSortDirection.Descending\n                                    : ListSortDirection.Ascending;\n                }\n\n                view.SortDescriptions.Clear();\n\n                GridViewColumnHeader currentSortedColumnHeader = GetSortedColumnHeader(listView);\n                if (currentSortedColumnHeader != null)\n                {\n                    RemoveSortGlyph(currentSortedColumnHeader);\n                }\n            }\n\n            if (!string.IsNullOrEmpty(propertyName))\n            {\n                view.SortDescriptions.Add(new SortDescription(propertyName, direction));\n                if (GetShowSortGlyph(listView))\n                {\n                    var glyph = direction == ListSortDirection.Ascending\n                                    ? GetSortGlyphAscending(listView)\n                                    : GetSortGlyphDescending(listView);\n                    AddSortGlyph(sortedColumnHeader, direction, glyph);\n                }\n\n                SetSortedColumnHeader(listView, sortedColumnHeader);\n            }\n        }\n\n        [SuppressMessage(\n            \"Microsoft.Design\",\n            \"CA1004:GenericMethodsShouldProvideTypeParameter\",\n            Justification = \"The generic style registration is desired, despite this rule.\")]\n        public static T GetAncestor<T>(DependencyObject reference)\n            where T : DependencyObject\n        {\n            var parent = VisualTreeHelper.GetParent(reference);\n            while (!(parent is T))\n            {\n                parent = VisualTreeHelper.GetParent(parent);\n            }\n\n            return (T)parent;\n        }\n\n        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...\n        public static bool GetAutoSort(DependencyObject dependencyObject)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            return (bool)dependencyObject.GetValue(AutoSortProperty);\n        }\n\n        public static ICommand GetCommand(DependencyObject dependencyObject)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            return (ICommand)dependencyObject.GetValue(CommandProperty);\n        }\n\n        // Using a DependencyProperty as the backing store for AutoSort.  This enables animation, styling, binding, etc...\n        public static string GetPropertyName(DependencyObject dependencyObject)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            return (string)dependencyObject.GetValue(PropertyNameProperty);\n        }\n\n        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...\n        public static bool GetShowSortGlyph(DependencyObject dependencyObject)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            return (bool)dependencyObject.GetValue(ShowSortGlyphProperty);\n        }\n\n        // Using a DependencyProperty as the backing store for ShowSortGlyph.  This enables animation, styling, binding, etc...\n        public static ImageSource GetSortGlyphAscending(DependencyObject dependencyObject)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            return (ImageSource)dependencyObject.GetValue(SortGlyphAscendingProperty);\n        }\n\n        // Using a DependencyProperty as the backing store for SortGlyphAscending.  This enables animation, styling, binding, etc...\n        public static ImageSource GetSortGlyphDescending(DependencyObject dependencyObject)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            return (ImageSource)dependencyObject.GetValue(SortGlyphDescendingProperty);\n        }\n\n        public static void SetAutoSort(DependencyObject dependencyObject, bool value)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            dependencyObject.SetValue(AutoSortProperty, value);\n        }\n\n        public static void SetCommand(DependencyObject dependencyObject, ICommand value)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            dependencyObject.SetValue(CommandProperty, value);\n        }\n\n        public static void SetPropertyName(DependencyObject dependencyObject, string value)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            dependencyObject.SetValue(PropertyNameProperty, value);\n        }\n\n        public static void SetShowSortGlyph(DependencyObject dependencyObject, bool value)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            dependencyObject.SetValue(ShowSortGlyphProperty, value);\n        }\n\n        public static void SetSortGlyphAscending(DependencyObject dependencyObject, ImageSource value)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            dependencyObject.SetValue(SortGlyphAscendingProperty, value);\n        }\n\n        public static void SetSortGlyphDescending(DependencyObject dependencyObject, ImageSource value)\n        {\n            dependencyObject.ThrowIfNull(nameof(dependencyObject));\n            dependencyObject.SetValue(SortGlyphDescendingProperty, value);\n        }\n\n        private static void AddSortGlyph(\n            GridViewColumnHeader columnHeader,\n            ListSortDirection direction,\n            ImageSource sortGlyph)\n        {\n            var adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);\n            adornerLayer.Add(new SortGlyphAdorner(columnHeader, direction, sortGlyph));\n        }\n\n        private static void ColumnHeaderClick(object sender, RoutedEventArgs e)\n        {\n            var headerClicked = e.OriginalSource as GridViewColumnHeader;\n            if (headerClicked != null && headerClicked.Column != null)\n            {\n                string propertyName = GetPropertyName(headerClicked.Column);\n                if (!string.IsNullOrEmpty(propertyName))\n                {\n                    var listView = GetAncestor<ListView>(headerClicked);\n                    if (listView != null)\n                    {\n                        var command = GetCommand(listView);\n                        if (command != null)\n                        {\n                            if (command.CanExecute(propertyName))\n                            {\n                                command.Execute(propertyName);\n                            }\n                        }\n                        else if (GetAutoSort(listView))\n                        {\n                            lock (listView.Items)\n                            {\n                                ApplySort(listView.Items, propertyName, listView, headerClicked);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        private static GridViewColumnHeader GetSortedColumnHeader(DependencyObject obj)\n        {\n            return (GridViewColumnHeader)obj.GetValue(SortedColumnHeaderProperty);\n        }\n\n        private static void RemoveSortGlyph(GridViewColumnHeader columnHeader)\n        {\n            var adornerLayer = AdornerLayer.GetAdornerLayer(columnHeader);\n            var adornerCollection = adornerLayer.GetAdorners(columnHeader);\n            if (adornerCollection != null)\n            {\n                foreach (var adorner in adornerCollection.OfType<SortGlyphAdorner>())\n                {\n                    adornerLayer.Remove(adorner);\n                }\n            }\n        }\n\n        private static void SetSortedColumnHeader(DependencyObject obj, GridViewColumnHeader value)\n        {\n            obj.SetValue(SortedColumnHeaderProperty, value);\n        }\n\n        private class SortGlyphAdorner : Adorner\n        {\n            private readonly GridViewColumnHeader columnHeader;\n\n            private readonly ListSortDirection direction;\n\n            private readonly ImageSource sortGlyph;\n\n            public SortGlyphAdorner(\n                GridViewColumnHeader columnHeader,\n                ListSortDirection direction,\n                ImageSource sortGlyph)\n                : base(columnHeader)\n            {\n                this.columnHeader = columnHeader;\n                this.direction = direction;\n                this.sortGlyph = sortGlyph;\n            }\n\n            protected override void OnRender(DrawingContext drawingContext)\n            {\n                base.OnRender(drawingContext);\n\n                if (sortGlyph != null)\n                {\n                    double x = columnHeader.ActualWidth - 13;\n                    double y = (columnHeader.ActualHeight / 2) - 5;\n                    Rect rect = new Rect(x, y, 10, 10);\n                    drawingContext.DrawImage(sortGlyph, rect);\n                }\n                else\n                {\n                    drawingContext.DrawGeometry(Brushes.LightGray, new Pen(Brushes.Gray, 1.0), GetDefaultGlyph());\n                }\n            }\n\n            private Geometry GetDefaultGlyph()\n            {\n                var x1 = columnHeader.ActualWidth - 13;\n                var x2 = x1 + 10;\n                var x3 = x1 + 5;\n                var y1 = (columnHeader.ActualHeight / 2) - 3;\n                var y2 = y1 + 5;\n\n                if (direction == ListSortDirection.Ascending)\n                {\n                    var tmp = y1;\n                    y1 = y2;\n                    y2 = tmp;\n                }\n\n                var pathSegmentCollection = new PathSegmentCollection\n                                                {\n                                                    new LineSegment(new Point(x2, y1), true),\n                                                    new LineSegment(new Point(x3, y2), true),\n                                                };\n\n                var pathFigure = new PathFigure(new Point(x1, y1), pathSegmentCollection, true);\n\n                var pathFigureCollection = new PathFigureCollection { pathFigure };\n\n                var pathGeometry = new PathGeometry(pathFigureCollection);\n                return pathGeometry;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/JsonHelper.cs",
    "content": "namespace Sentinel.Support\n{\n    using System;\n    using System.IO;\n\n    using log4net;\n\n    using Newtonsoft.Json;\n\n    public static class JsonHelper\n    {\n        private static readonly ILog Log = LogManager.GetLogger(nameof(JsonHelper));\n\n        private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings\n                                                                      {\n                                                                          TypeNameHandling = TypeNameHandling.All,\n                                                                      };\n\n        public static void SerializeToFile<T>(T objectToSerialize, string filename)\n        {\n            try\n            {\n                var objectString = JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, Settings);\n\n                var fi = new FileInfo(filename);\n                Stream fs = null;\n                try\n                {\n                    fs = fi.Open(FileMode.Create, FileAccess.Write);\n                    using (var sw = new StreamWriter(fs))\n                    {\n                        fs = null;\n                        sw.Write(objectString);\n                    }\n                }\n                finally\n                {\n                    fs?.Dispose();\n                }\n            }\n            catch (Exception e)\n            {\n                Log.Error(\"Exception caught in serialization:\", e);\n                throw;\n            }\n        }\n\n        public static string SerializeToString<T>(T objectToSerialize)\n        {\n            try\n            {\n                return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, Settings);\n            }\n            catch (Exception e)\n            {\n                Log.Error(\"Exception caught in serialization:\", e);\n                throw;\n            }\n        }\n\n        public static T DeserializeFromFile<T>(string filename)\n        {\n            try\n            {\n                var fi = new FileInfo(filename);\n\n                if (fi.Exists)\n                {\n                    Stream fs = null;\n                    try\n                    {\n                        fs = fi.OpenRead();\n                        using (var sr = new StreamReader(fs))\n                        {\n                            fs = null;\n                            var asString = sr.ReadToEnd();\n                            return JsonConvert.DeserializeObject<T>(asString, Settings);\n                        }\n                    }\n                    finally\n                    {\n                        fs?.Dispose();\n                    }\n                }\n            }\n            catch (Exception e)\n            {\n                Log.Error($\"Exception when trying to de-serialize from {filename}\", e);\n            }\n\n            return default(T);\n        }\n\n        public static T DeserializeFromString<T>(string value)\n        {\n            try\n            {\n                return JsonConvert.DeserializeObject<T>(value, Settings);\n            }\n            catch (Exception e)\n            {\n                Log.Error(\"Exception when trying to de-serialize from given string\", e);\n            }\n\n            return default(T);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/ScrollingHelper.cs",
    "content": "﻿namespace Sentinel.Support\n{\n    using System;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Media;\n    using System.Windows.Threading;\n\n    public static class ScrollingHelper\n    {\n        public delegate void VoidFunctionHandler(ListBox listBox);\n\n        public static Visual GetDescendantByType(Visual element, Type type)\n        {\n            if (element != null)\n            {\n                if (element.GetType() != type)\n                {\n                    Visual foundElement = null;\n                    (element as FrameworkElement)?.ApplyTemplate();\n\n                    for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)\n                    {\n                        var visual = VisualTreeHelper.GetChild(element, i) as Visual;\n                        foundElement = GetDescendantByType(visual, type);\n                        if (foundElement != null)\n                        {\n                            break;\n                        }\n                    }\n\n                    return foundElement;\n                }\n\n                return element;\n            }\n\n            return null;\n        }\n\n        public static void ScrollToEnd(Dispatcher dispatcher, ListBox listBox)\n        {\n            if (dispatcher.CheckAccess())\n            {\n                SelectLastEntry(listBox);\n            }\n            else\n            {\n                dispatcher.BeginInvoke(\n                    DispatcherPriority.Send,\n                    new VoidFunctionHandler(SelectLastEntry),\n                    listBox);\n            }\n        }\n\n        private static void SelectLastEntry(ListBox listBox)\n        {\n            var scrollViewer = GetDescendantByType(listBox, typeof(ScrollViewer)) as ScrollViewer;\n            scrollViewer?.ScrollToEnd();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Wpf/DataBoundToolbar.cs",
    "content": "﻿namespace Sentinel.Support.Wpf\n{\n    using System.Windows.Controls;\n    using System.Windows.Threading;\n\n    public class DataBoundToolbar : ToolBar\n    {\n        private delegate void InvalidateMeasurementDelegate();\n\n        public override void OnApplyTemplate()\n        {\n            Dispatcher.BeginInvoke(\n                new InvalidateMeasurementDelegate(InvalidateMeasure),\n                DispatcherPriority.Background,\n                null);\n            base.OnApplyTemplate();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Wpf/FixedWidthColumn.cs",
    "content": "namespace Sentinel.Support.Wpf\n{\n    using System.Windows;\n    using System.Windows.Controls;\n\n    public class FixedWidthColumn : GridViewColumn\n    {\n        public static readonly DependencyProperty FixedWidthProperty =\n            DependencyProperty.Register(\n                \"FixedWidth\",\n                typeof(double),\n                typeof(FixedWidthColumn),\n                new FrameworkPropertyMetadata(double.NaN, OnFixedWidthChanged));\n\n        static FixedWidthColumn()\n        {\n            WidthProperty.OverrideMetadata(\n                typeof(FixedWidthColumn),\n                new FrameworkPropertyMetadata(null, OnCoerceWidth));\n        }\n\n        public double FixedWidth\n        {\n            get\n            {\n                return (double)GetValue(FixedWidthProperty);\n            }\n\n            set\n            {\n                SetValue(FixedWidthProperty, value);\n            }\n        }\n\n        private static object OnCoerceWidth(DependencyObject o, object baseValue)\n        {\n            var fwc = o as FixedWidthColumn;\n            return fwc?.FixedWidth ?? baseValue;\n        }\n\n        private static void OnFixedWidthChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)\n        {\n            var fwc = o as FixedWidthColumn;\n            fwc?.CoerceValue(WidthProperty);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Wpf/ObservableDictionary.cs",
    "content": "﻿namespace Sentinel.Support.Wpf\n{\n    using System;\n    using System.Collections;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.Collections.Specialized;\n    using System.ComponentModel;\n    using System.Runtime.InteropServices;\n    using System.Runtime.Serialization;\n    using Sentinel.Interfaces.CodeContracts;\n\n    [Serializable]\n    public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>,\n                                                      IDictionary,\n                                                      ISerializable,\n                                                      IDeserializationCallback,\n                                                      INotifyCollectionChanged,\n                                                      INotifyPropertyChanged\n    {\n        [NonSerialized]\n        private readonly SerializationInfo serializationInfo;\n\n        private readonly Dictionary<TKey, TValue> dictionaryCache = new Dictionary<TKey, TValue>();\n\n        private int countCache;\n\n        private int dictionaryCacheVersion;\n\n        private int version;\n\n        public ObservableDictionary()\n        {\n            KeyedEntryCollection = new KeyedDictionaryEntryCollection();\n        }\n\n        public ObservableDictionary(IDictionary<TKey, TValue> dictionary)\n        {\n            dictionary.ThrowIfNull(nameof(dictionary));\n\n            KeyedEntryCollection = new KeyedDictionaryEntryCollection();\n\n            foreach (var entry in dictionary)\n            {\n                DoAddEntry(entry.Key, entry.Value);\n            }\n        }\n\n        public ObservableDictionary(IEqualityComparer<TKey> comparer)\n        {\n            KeyedEntryCollection = new KeyedDictionaryEntryCollection(comparer);\n        }\n\n        public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)\n        {\n            dictionary.ThrowIfNull(nameof(dictionary));\n\n            KeyedEntryCollection = new KeyedDictionaryEntryCollection(comparer);\n\n            foreach (var entry in dictionary)\n            {\n                DoAddEntry(entry.Key, entry.Value);\n            }\n        }\n\n        protected ObservableDictionary(SerializationInfo info, StreamingContext context)\n        {\n            serializationInfo = info;\n        }\n\n        protected virtual event NotifyCollectionChangedEventHandler CollectionChanged;\n\n        protected virtual event PropertyChangedEventHandler PropertyChanged;\n\n        event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged\n        {\n            add\n            {\n                CollectionChanged += value;\n            }\n\n            remove\n            {\n                CollectionChanged -= value;\n            }\n        }\n\n        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged\n        {\n            add\n            {\n                PropertyChanged += value;\n            }\n\n            remove\n            {\n                PropertyChanged -= value;\n            }\n        }\n\n        public IEqualityComparer<TKey> Comparer => KeyedEntryCollection.Comparer;\n\n        public int Count => KeyedEntryCollection.Count;\n\n        public Dictionary<TKey, TValue>.KeyCollection Keys => TrueDictionary.Keys;\n\n        public Dictionary<TKey, TValue>.ValueCollection Values => TrueDictionary.Values;\n\n        int ICollection.Count => KeyedEntryCollection.Count;\n\n        bool IDictionary.IsFixedSize => false;\n\n        bool IDictionary.IsReadOnly => false;\n\n        bool ICollection.IsSynchronized => ((ICollection)KeyedEntryCollection).IsSynchronized;\n\n        ICollection IDictionary.Keys => Keys;\n\n        object ICollection.SyncRoot => ((ICollection)KeyedEntryCollection).SyncRoot;\n\n        ICollection IDictionary.Values => Values;\n\n        int ICollection<KeyValuePair<TKey, TValue>>.Count => KeyedEntryCollection.Count;\n\n        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;\n\n        ICollection<TKey> IDictionary<TKey, TValue>.Keys => Keys;\n\n        ICollection<TValue> IDictionary<TKey, TValue>.Values => Values;\n\n        protected KeyedDictionaryEntryCollection KeyedEntryCollection { get; }\n\n        private Dictionary<TKey, TValue> TrueDictionary\n        {\n            get\n            {\n                if (dictionaryCacheVersion != version)\n                {\n                    dictionaryCache.Clear();\n                    foreach (var entry in KeyedEntryCollection)\n                    {\n                        dictionaryCache.Add((TKey)entry.Key, (TValue)entry.Value);\n                    }\n\n                    dictionaryCacheVersion = version;\n                }\n\n                return dictionaryCache;\n            }\n        }\n\n        public TValue this[TKey key]\n        {\n            get => (TValue)KeyedEntryCollection[key].Value;\n            set => DoSetEntry(key, value);\n        }\n\n        object IDictionary.this[object key]\n        {\n            get => KeyedEntryCollection[(TKey)key].Value;\n            set => DoSetEntry((TKey)key, (TValue)value);\n        }\n\n        TValue IDictionary<TKey, TValue>.this[TKey key]\n        {\n            get => (TValue)KeyedEntryCollection[key].Value;\n            set => DoSetEntry(key, value);\n        }\n\n        public void Add(TKey key, TValue value)\n        {\n            DoAddEntry(key, value);\n        }\n\n        public void Clear()\n        {\n            DoClearEntries();\n        }\n\n        public bool ContainsKey(TKey key) => KeyedEntryCollection.Contains(key);\n\n        public bool ContainsValue(TValue value) => TrueDictionary.ContainsValue(value);\n\n        public IEnumerator GetEnumerator() => new Enumerator(this, false);\n\n        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)\n        {\n            info.ThrowIfNull(nameof(info));\n\n            var entries = new Collection<DictionaryEntry>();\n            foreach (var entry in KeyedEntryCollection)\n            {\n                entries.Add(entry);\n            }\n\n            info.AddValue(\"entries\", entries);\n        }\n\n        public virtual void OnDeserialization(object sender)\n        {\n            if (serializationInfo != null)\n            {\n                var entries = (Collection<DictionaryEntry>)serializationInfo.GetValue(\"entries\", typeof(Collection<DictionaryEntry>));\n                foreach (var entry in entries)\n                {\n                    AddEntry((TKey)entry.Key, (TValue)entry.Value);\n                }\n            }\n        }\n\n        public bool Remove(TKey key) => DoRemoveEntry(key);\n\n        public bool TryGetValue(TKey key, out TValue value)\n        {\n            var result = KeyedEntryCollection.Contains(key);\n            value = result ? (TValue)KeyedEntryCollection[key].Value : default(TValue);\n            return result;\n        }\n\n        void IDictionary.Add(object key, object value)\n        {\n            DoAddEntry((TKey)key, (TValue)value);\n        }\n\n        void IDictionary.Clear()\n        {\n            DoClearEntries();\n        }\n\n        bool IDictionary.Contains(object key)\n        {\n            return KeyedEntryCollection.Contains((TKey)key);\n        }\n\n        void ICollection.CopyTo(Array array, int index)\n        {\n            ((ICollection)KeyedEntryCollection).CopyTo(array, index);\n        }\n\n        IDictionaryEnumerator IDictionary.GetEnumerator()\n        {\n            return new Enumerator(this, true);\n        }\n\n        void IDictionary.Remove(object key)\n        {\n            DoRemoveEntry((TKey)key);\n        }\n\n        void IDictionary<TKey, TValue>.Add(TKey key, TValue value)\n        {\n            DoAddEntry(key, value);\n        }\n\n        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> kvp)\n        {\n            DoAddEntry(kvp.Key, kvp.Value);\n        }\n\n        void ICollection<KeyValuePair<TKey, TValue>>.Clear()\n        {\n            DoClearEntries();\n        }\n\n        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> kvp)\n        {\n            return KeyedEntryCollection.Contains(kvp.Key);\n        }\n\n        bool IDictionary<TKey, TValue>.ContainsKey(TKey key)\n        {\n            return KeyedEntryCollection.Contains(key);\n        }\n\n        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index)\n        {\n            array.ThrowIfNull(nameof(array));\n\n            if (index < 0 || index > array.Length)\n            {\n                throw new ArgumentOutOfRangeException(\n                    nameof(index),\n                    \"CopyTo() failed:  index parameter was outside the bounds of the supplied array\");\n            }\n\n            if (array.Length - index < KeyedEntryCollection.Count)\n            {\n                throw new ArgumentException(\"CopyTo() failed:  supplied array was too small\", nameof(array));\n            }\n\n            foreach (var entry in KeyedEntryCollection)\n            {\n                array[index++] = new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value);\n            }\n        }\n\n        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()\n        {\n            return new Enumerator(this, false);\n        }\n\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            return GetEnumerator();\n        }\n\n        bool IDictionary<TKey, TValue>.Remove(TKey key)\n        {\n            return DoRemoveEntry(key);\n        }\n\n        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> kvp)\n        {\n            return DoRemoveEntry(kvp.Key);\n        }\n\n        bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)\n        {\n            return TryGetValue(key, out value);\n        }\n\n        protected virtual bool AddEntry(TKey key, TValue value)\n        {\n            KeyedEntryCollection.Add(new DictionaryEntry(key, value));\n            return true;\n        }\n\n        protected virtual bool ClearEntries()\n        {\n            // check whether there are entries to clear\n            var result = Count > 0;\n            if (result)\n            {\n                // if so, clear the dictionary\n                KeyedEntryCollection.Clear();\n            }\n\n            return result;\n        }\n\n        protected int GetIndexAndEntryForKey(TKey key, out DictionaryEntry entry)\n        {\n            entry = default(DictionaryEntry);\n            var index = -1;\n            if (KeyedEntryCollection.Contains(key))\n            {\n                entry = KeyedEntryCollection[key];\n                index = KeyedEntryCollection.IndexOf(entry);\n            }\n\n            return index;\n        }\n\n        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)\n        {\n            CollectionChanged?.Invoke(this, args);\n        }\n\n        protected virtual void OnPropertyChanged(string name)\n        {\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n        }\n\n        protected virtual bool RemoveEntry(TKey key)\n        {\n            // remove the entry\n            return KeyedEntryCollection.Remove(key);\n        }\n\n        protected virtual bool SetEntry(TKey key, TValue value)\n        {\n            var keyExists = KeyedEntryCollection.Contains(key);\n\n            // if identical key/value pair already exists, nothing to do\n            if (keyExists && value.Equals((TValue)KeyedEntryCollection[key].Value))\n            {\n                return false;\n            }\n\n            // otherwise, remove the existing entry\n            if (keyExists)\n            {\n                KeyedEntryCollection.Remove(key);\n            }\n\n            // add the new entry\n            KeyedEntryCollection.Add(new DictionaryEntry(key, value));\n\n            return true;\n        }\n\n        private void DoAddEntry(TKey key, TValue value)\n        {\n            if (AddEntry(key, value))\n            {\n                version++;\n\n                DictionaryEntry entry;\n                var index = GetIndexAndEntryForKey(key, out entry);\n                FireEntryAddedNotifications(entry, index);\n            }\n        }\n\n        private void DoClearEntries()\n        {\n            if (ClearEntries())\n            {\n                version++;\n                FireResetNotifications();\n            }\n        }\n\n        private bool DoRemoveEntry(TKey key)\n        {\n            DictionaryEntry entry;\n            var index = GetIndexAndEntryForKey(key, out entry);\n\n            var result = RemoveEntry(key);\n            if (result)\n            {\n                version++;\n                if (index > -1)\n                {\n                    FireEntryRemovedNotifications(entry, index);\n                }\n            }\n\n            return result;\n        }\n\n        private void DoSetEntry(TKey key, TValue value)\n        {\n            DictionaryEntry entry;\n            var index = GetIndexAndEntryForKey(key, out entry);\n\n            if (SetEntry(key, value))\n            {\n                version++;\n\n                // if prior entry existed for this key, fire the removed notifications\n                if (index > -1)\n                {\n                    FireEntryRemovedNotifications(entry, index);\n\n                    // force the property change notifications to fire for the modified entry\n                    countCache--;\n                }\n\n                // then fire the added notifications\n                index = GetIndexAndEntryForKey(key, out entry);\n                FireEntryAddedNotifications(entry, index);\n            }\n        }\n\n        private void FireEntryAddedNotifications(DictionaryEntry entry, int index)\n        {\n            // fire the relevant PropertyChanged notifications\n            FirePropertyChangedNotifications();\n\n            // fire CollectionChanged notification\n            if (index > -1)\n            {\n                OnCollectionChanged(\n                    new NotifyCollectionChangedEventArgs(\n                        NotifyCollectionChangedAction.Add,\n                        new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value),\n                        index));\n            }\n            else\n            {\n                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));\n            }\n        }\n\n        private void FireEntryRemovedNotifications(DictionaryEntry entry, int index)\n        {\n            // fire the relevant PropertyChanged notifications\n            FirePropertyChangedNotifications();\n\n            // fire CollectionChanged notification\n            if (index > -1)\n            {\n                OnCollectionChanged(\n                    new NotifyCollectionChangedEventArgs(\n                        NotifyCollectionChangedAction.Remove,\n                        new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value),\n                        index));\n            }\n            else\n            {\n                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));\n            }\n        }\n\n        private void FirePropertyChangedNotifications()\n        {\n            if (Count != countCache)\n            {\n                countCache = Count;\n                OnPropertyChanged(\"Count\");\n                OnPropertyChanged(\"Item[]\");\n                OnPropertyChanged(\"Keys\");\n                OnPropertyChanged(\"Values\");\n            }\n        }\n\n        private void FireResetNotifications()\n        {\n            // fire the relevant PropertyChanged notifications\n            FirePropertyChangedNotifications();\n\n            // fire CollectionChanged notification\n            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));\n        }\n\n        [Serializable]\n        [StructLayout(LayoutKind.Sequential)]\n        public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator\n        {\n            private readonly ObservableDictionary<TKey, TValue> dictionary;\n\n            private readonly int version;\n\n            private readonly bool isDictionaryEntryEnumerator;\n\n            private int index;\n\n            private KeyValuePair<TKey, TValue> current;\n\n            internal Enumerator(ObservableDictionary<TKey, TValue> dictionary, bool isDictionaryEntryEnumerator)\n            {\n                this.dictionary = dictionary;\n                version = dictionary.version;\n                index = -1;\n                this.isDictionaryEntryEnumerator = isDictionaryEntryEnumerator;\n                current = default(KeyValuePair<TKey, TValue>);\n            }\n\n            object IEnumerator.Current\n            {\n                get\n                {\n                    ValidateCurrent();\n                    if (isDictionaryEntryEnumerator)\n                    {\n                        return new DictionaryEntry(current.Key, current.Value);\n                    }\n\n                    return new KeyValuePair<TKey, TValue>(current.Key, current.Value);\n                }\n            }\n\n            public KeyValuePair<TKey, TValue> Current\n            {\n                get\n                {\n                    ValidateCurrent();\n                    return current;\n                }\n            }\n\n            object IDictionaryEnumerator.Key\n            {\n                get\n                {\n                    ValidateCurrent();\n                    return current.Key;\n                }\n            }\n\n            DictionaryEntry IDictionaryEnumerator.Entry\n            {\n                get\n                {\n                    ValidateCurrent();\n                    return new DictionaryEntry(current.Key, current.Value);\n                }\n            }\n\n            object IDictionaryEnumerator.Value\n            {\n                get\n                {\n                    ValidateCurrent();\n                    return current.Value;\n                }\n            }\n\n            public void Dispose()\n            {\n            }\n\n            public bool MoveNext()\n            {\n                ValidateVersion();\n                index++;\n                if (index < dictionary.KeyedEntryCollection.Count)\n                {\n                    current = new KeyValuePair<TKey, TValue>(\n                        (TKey)dictionary.KeyedEntryCollection[index].Key,\n                        (TValue)dictionary.KeyedEntryCollection[index].Value);\n                    return true;\n                }\n\n                index = -2;\n                current = default(KeyValuePair<TKey, TValue>);\n                return false;\n            }\n\n            void IEnumerator.Reset()\n            {\n                ValidateVersion();\n                index = -1;\n                current = default(KeyValuePair<TKey, TValue>);\n            }\n\n            private void ValidateVersion()\n            {\n                if (version != dictionary.version)\n                {\n                    throw new InvalidOperationException(\"The enumerator is not valid because the dictionary changed.\");\n                }\n            }\n\n            private void ValidateCurrent()\n            {\n                switch (index)\n                {\n                    case -1:\n                        throw new InvalidOperationException(\"The enumerator has not been started.\");\n                    case -2:\n                        throw new InvalidOperationException(\"The enumerator has reached the end of the collection.\");\n                }\n            }\n        }\n\n        protected class KeyedDictionaryEntryCollection : KeyedCollection<TKey, DictionaryEntry>\n        {\n            public KeyedDictionaryEntryCollection()\n            {\n            }\n\n            public KeyedDictionaryEntryCollection(IEqualityComparer<TKey> comparer)\n                : base(comparer)\n            {\n            }\n\n            protected override TKey GetKeyForItem(DictionaryEntry entry)\n            {\n                return (TKey)entry.Key;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Support/Wpf/ThemeInfo.cs",
    "content": "﻿namespace Sentinel.Support.Wpf\n{\n    using System;\n    using System.IO;\n    using System.Runtime.InteropServices;\n    using System.Text;\n\n    public static class ThemeInfo\n    {\n        private const int MaxPath = 260;\n\n        // Cache the name of the current theme.\n        private static string currentTheme = string.Empty;\n\n        /// <summary>\n        /// Gets the filename of the current theme.\n        /// </summary>\n        /// <remarks>\n        /// Will return an empty string if themes are not supported by\n        /// the operating system or disabled by the user.\n        /// </remarks>\n        public static string CurrentThemeFileName\n        {\n            get\n            {\n                if (string.IsNullOrEmpty(currentTheme))\n                {\n                    if (IsSupportedByOS)\n                    {\n                        if (!IsEnabledByUser)\n                        {\n                            currentTheme = string.Empty;\n                        }\n\n                        var name = new StringBuilder(MaxPath, MaxPath);\n                        SafeNativeMethods.GetCurrentThemeName(name, MaxPath, IntPtr.Zero, 0, IntPtr.Zero, 0);\n\n                        currentTheme = Path.GetFileNameWithoutExtension(name.ToString());\n                    }\n                }\n\n                return currentTheme;\n            }\n        }\n\n        /// <summary>\n        /// Gets a value indicating whether the user has enabled visual styles in the operating system.\n        /// </summary>\n        public static bool IsEnabledByUser => IsSupportedByOS && SafeNativeMethods.IsThemeActive();\n\n        /// <summary>\n        /// Gets a value indicating whether the operating system supports visual styles.\n        /// </summary>\n        public static bool IsSupportedByOS => Environment.OSVersion.Platform == PlatformID.Win32NT\n                                              && Environment.OSVersion.Version >= new Version(5, 1);\n\n        internal abstract class SafeNativeMethods\n        {\n            /// <summary>\n            /// Retrieves the name of the current visual style.\n            /// </summary>\n            /// <param name=\"pszThemeFileName\">Pointer to a string that receives the theme path and file name.</param>\n            /// <param name=\"maxNameChars\">Value that contains the maximum number of characters allowed in the theme file name.</param>\n            /// <param name=\"sizeColourBuffer\">Pointer to a string that receives the color scheme name. This parameter may be set to NULL.</param>\n            /// <param name=\"cchMaxColorChars\">Value of type int that contains the maximum number of characters allowed in the color scheme name.</param>\n            /// <param name=\"buffer\">Pointer to a string that receives the size name. This parameter may be set to NULL.</param>\n            /// <param name=\"maxBufferSize\">Value of type int that contains the maximum number of characters allowed in the size name.</param>\n            /// <returns>Returns S_OK if successful, otherwise an error code.</returns>\n            [DllImport(\"UxTheme.dll\", CharSet = CharSet.Unicode, EntryPoint = \"GetCurrentThemeName\", SetLastError = true)]\n            internal static extern int GetCurrentThemeName(\n                [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszThemeFileName,\n                int maxNameChars,\n                IntPtr sizeColourBuffer,\n                int cchMaxColorChars,\n                IntPtr buffer,\n                int maxBufferSize);\n\n            /// <summary>\n            /// Tests if a visual style for the current application is active.\n            /// </summary>\n            /// <returns>Boolean value indicating whether the use of themes is active.</returns>\n            [DllImport(\"UxTheme.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n            internal static extern bool IsThemeActive();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Gui/LogMessages.cs",
    "content": "﻿namespace Sentinel.Views.Gui\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows.Controls;\n    using System.Windows.Threading;\n    using Sentinel.Extractors.Interfaces;\n    using Sentinel.Filters.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Services;\n    using Sentinel.Views.Interfaces;\n    using WpfExtras;\n\n    public class LogMessages : ViewModelBase, ILogViewer\n    {\n        public static readonly IViewInformation Info = new ViewInformation(ID, NAME);\n\n        private const string ID = \"f4d8c068-bf72-4b83-9d4a-1cd8a89fea11\";\n\n        private const string NAME = \"Log viewer\";\n\n        private const string DESCRIPTION = \"Traditional row based log view with highlighting and incremental search.\";\n\n        private readonly IFilteringService<IFilter> filteringService;\n\n        private readonly IExtractingService<IExtractor> extractingService;\n\n        private readonly Queue<ILogEntry> pendingAdditions = new Queue<ILogEntry>();\n\n        private readonly LogMessagesControl presenter;\n\n        private bool clearPending;\n\n        private int filteredCount;\n\n        private ILogger logger;\n\n        private bool rebuildList;\n\n        private string status;\n\n        private int unfilteredCount;\n\n        private bool autoScroll = true;\n\n        public LogMessages()\n        {\n            ((ViewInformation)Info).Description = DESCRIPTION;\n            presenter = new LogMessagesControl\n            {\n                DataContext = this,\n            };\n\n            Messages = new ObservableCollection<ILogEntry>();\n\n            PropertyChanged += PropertyChangedHandler;\n\n            var dt = new DispatcherTimer(DispatcherPriority.Normal)\n            {\n                Interval = TimeSpan.FromMilliseconds(200),\n            };\n            dt.Tick += UpdateTick;\n            dt.Start();\n\n            filteringService = ServiceLocator.Instance.Get<IFilteringService<IFilter>>();\n            if (filteringService != null)\n            {\n                if (filteringService is INotifyPropertyChanged notify)\n                {\n                    notify.PropertyChanged += (sender, e) => ApplyFiltering();\n                }\n            }\n\n            extractingService = ServiceLocator.Instance.Get<IExtractingService<IExtractor>>();\n            if (extractingService != null)\n            {\n                if (extractingService is INotifyPropertyChanged notify)\n                {\n                    notify.PropertyChanged += (sender, e) => ApplyExtracting();\n                }\n            }\n\n            Preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n            if (Preferences != null)\n            {\n                if (Preferences is INotifyPropertyChanged notify)\n                {\n                    notify.PropertyChanged += (sender, args) =>\n                    {\n                        var prop = args.PropertyName;\n                        switch (prop)\n                        {\n                            case \"SelectedTimeFormatOption\":\n                            case \"ConvertUtcTimesToLocalTimeZone\":\n                            case \"SelectedDateOption\":\n                                rebuildList = true;\n                                break;\n                        }\n                    };\n                }\n            }\n\n            InitialiseToolbar();\n        }\n\n        public ObservableCollection<ILogEntry> Messages { get; private set; }\n\n        /// <summary>\n        /// Gets or sets the count of filtered entries.\n        /// </summary>\n        public int FilteredCount\n        {\n            get => filteredCount;\n\n            set\n            {\n                if (filteredCount != value)\n                {\n                    filteredCount = value;\n                    OnPropertyChanged(nameof(FilteredCount));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets or sets the count of unfiltered entries.\n        /// </summary>\n        public int UnfilteredCount\n        {\n            get => unfilteredCount;\n\n            set\n            {\n                if (unfilteredCount != value)\n                {\n                    unfilteredCount = value;\n                    OnPropertyChanged(nameof(UnfilteredCount));\n                }\n            }\n        }\n\n        public string Status\n        {\n            get => status;\n\n            private set\n            {\n                if (status == value)\n                {\n                    return;\n                }\n\n                status = value;\n                OnPropertyChanged(nameof(Status));\n            }\n        }\n\n        /// <summary>\n        /// Gets the name of a LogViewer.\n        /// </summary>\n        public string Name => Info.Name;\n\n        public IEnumerable<ILogViewerToolbarButton> ToolbarItems { get; private set; }\n\n        public ILogger Logger\n        {\n            get => logger;\n\n            private set\n            {\n                if (logger != value)\n                {\n                    logger = value;\n                    OnPropertyChanged(nameof(Logger));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Gets the Presenter control for a log viewer.\n        /// </summary>\n        public Control Presenter => presenter;\n\n        private IUserPreferences Preferences { get; }\n\n        public void SetLogger(ILogger newLogger)\n        {\n            Logger = newLogger;\n        }\n\n        private void PauseMessagesHandler(object obj)\n        {\n            Debug.Assert(logger != null, \"Logger has not been instantiated\");\n            logger.Enabled = !logger.Enabled;\n        }\n\n        private void InitialiseToolbar()\n        {\n            var autoScrollButton = new LogViewerToolbarButton(\n                \"Auto-Scroll\",\n                \"Automatically scroll to show the newest entry\",\n                true,\n                new DelegateCommand(e => autoScroll = !autoScroll))\n            {\n                IsChecked = autoScroll,\n                ImageIdentifier = \"ScrollDown\",\n            };\n\n            var clearButton = new LogViewerToolbarButton(\n                \"Clear\",\n                \"Clear the log messages from the display\",\n                false,\n                new DelegateCommand(e => clearPending = true))\n            {\n                ImageIdentifier = \"Clear\",\n            };\n\n            var pauseButton = new LogViewerToolbarButton(\n                \"Pause\",\n                \"Pause the addition of messages to the display\",\n                true,\n                new DelegateCommand(PauseMessagesHandler))\n            {\n                IsChecked = false,\n                ImageIdentifier = \"Pause\",\n            };\n\n            var toolbar = new ObservableCollection<ILogViewerToolbarButton>\n            {\n                autoScrollButton,\n                clearButton,\n                pauseButton,\n            };\n\n            ToolbarItems = toolbar;\n        }\n\n        /// <summary>\n        /// Apply filtering to the the collection of log entries.\n        /// </summary>\n        private void ApplyFiltering()\n        {\n            lock (Messages)\n            {\n                Trace.WriteLine(\"Applying filters...\");\n\n                // About to get the full data set from the LogEntriesManager,\n                // therefore anything in the pendingQueue is unneeded as it\n                // will already be in the complete collection and the incomplete\n                // filtered copy of that list is going to be disposed.\n                lock (pendingAdditions)\n                {\n                    pendingAdditions.Clear();\n                }\n\n                rebuildList = true;\n            }\n        }\n\n        /// <summary>\n        /// Append new log entry, as long as it wouldn't normally have been filtered.\n        /// </summary>\n        /// <param name=\"entry\">Entry to add.</param>\n        private void AddIfPassesFilters(ILogEntry entry)\n        {\n            lock (Messages)\n            {\n                // If no filtering service or no extracting service, then assume it passes.\n                if (filteringService == null || extractingService == null)\n                {\n                    Messages.Add(entry);\n                }\n                else\n                {\n                    if (!filteringService.IsFiltered(entry) && !extractingService.IsFiltered(entry))\n                    {\n                        Messages.Add(entry);\n                    }\n                }\n            }\n        }\n\n        private void ApplyExtracting()\n        {\n            lock (Messages)\n            {\n                Trace.WriteLine(\"Applying extractors...\");\n\n                // About to get the full data set from the LogEntriesManager,\n                // therefore anything in the pendingQueue is unneeded as it\n                // will already be in the complete collection and the incomplete\n                // filtered copy of that list is going to be disposed.\n                lock (pendingAdditions)\n                {\n                    pendingAdditions.Clear();\n                }\n\n                rebuildList = true;\n            }\n        }\n\n        /// <summary>\n        /// Callback method called upon the delegate timer.  Rebuilds the list\n        /// or appends new entries based upon the state of <c>clearPending</c>,\n        /// <c>rebuildList</c> or the <c>pendingActions</c> collection containing\n        /// entries.\n        /// </summary>\n        /// <param name=\"sender\">Sender of the event.</param>\n        /// <param name=\"e\">Argument for the timer callback.</param>\n        private void UpdateTick(object sender, EventArgs e)\n        {\n            if (Logger == null)\n            {\n                return;\n            }\n\n            if (clearPending || rebuildList)\n            {\n                // If rebuilding the list, any additions to the pendingAdditions\n                // made since the \"rebuildList\" variable was set to true will\n                // not be needed, throw them away to avoid duplication.\n                lock (pendingAdditions)\n                {\n                    pendingAdditions.Clear();\n                }\n\n                lock (Messages)\n                {\n                    Messages.Clear();\n\n                    lock (Logger.Entries)\n                    {\n                        if (clearPending)\n                        {\n                            Logger.Clear();\n                        }\n\n                        foreach (var entry in Logger.Entries)\n                        {\n                            AddIfPassesFilters(entry);\n                        }\n                    }\n                }\n\n                // Reset the counters.\n                FilteredCount = 0;\n                UnfilteredCount = 0;\n\n                rebuildList = clearPending = false;\n            }\n            else if (pendingAdditions.Count > 0)\n            {\n                lock (pendingAdditions)\n                {\n                    lock (Messages)\n                    {\n                        while (pendingAdditions.Count > 0)\n                        {\n                            var entry = pendingAdditions.Dequeue();\n                            AddIfPassesFilters(entry);\n                        }\n\n                        if (Preferences?.LimitMessages ?? false)\n                        {\n                            var limitAsString = Preferences?.MaximumMessageCount;\n                            if (int.TryParse(limitAsString, out var limit))\n                            {\n                                Logger.LimitMessageCount(limit);\n\n                                // Ensure filtered view is also limited.\n                                var messages = Messages.Count;\n                                var excessMessages = messages - limit;\n\n                                if (excessMessages > 0)\n                                {\n                                    for (; excessMessages > 0; excessMessages--)\n                                    {\n                                        Messages.RemoveAt(0);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (autoScroll)\n                {\n                    presenter.ScrollToEnd();\n                }\n            }\n\n            FilteredCount = Messages.Count;\n            UnfilteredCount = Logger.Entries.Count();\n        }\n\n        private void LoggerPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"NewEntries\")\n            {\n                lock (Logger.NewEntries)\n                {\n                    lock (pendingAdditions)\n                    {\n                        foreach (var entry in Logger.NewEntries)\n                        {\n                            pendingAdditions.Enqueue(entry);\n                        }\n                    }\n                }\n            }\n            else if (e.PropertyName == \"Entries\")\n            {\n                lock (Messages)\n                {\n                    // Determine whether a clear has been instigated\n                    if (Messages.Count > Logger.Entries.Count())\n                    {\n                        Messages.Clear();\n                    }\n                }\n            }\n            else if (e.PropertyName == \"Enabled\")\n            {\n                var pauseButton = ToolbarItems.FirstOrDefault(c => c.Label == \"Pause\");\n                if (pauseButton != null)\n                {\n                    pauseButton.IsChecked = !logger.Enabled;\n                }\n            }\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"Logger\")\n            {\n                lock (Messages)\n                {\n                    // Get rid of any existing messages and populate with messages\n                    // from newly bound structure (if any).\n                    Messages.Clear();\n                }\n\n                // Register to the logger.\n                logger.PropertyChanged += LoggerPropertyChanged;\n            }\n            else if (e.PropertyName == \"FilteredCount\" || e.PropertyName == \"UnfilteredCount\")\n            {\n                lock (Messages)\n                {\n                    if (!Logger.Entries.Any())\n                    {\n                        Messages.Clear();\n                    }\n\n                    var filtered = FilteredCount < UnfilteredCount;\n                    Status = filtered\n                        ? $\"{FilteredCount} of {UnfilteredCount} Messages [Filters Applied]\"\n                        : $\"{UnfilteredCount} Messages\";\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Gui/LogMessagesControl.xaml",
    "content": "﻿<UserControl xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:Controls=\"clr-namespace:Sentinel.Support.Wpf\"\n             xmlns:Converters=\"clr-namespace:Sentinel.Support.Converters\"\n             xmlns:Windows=\"clr-namespace:Sentinel.Controls\"\n             mc:Ignorable=\"d\"\n             x:Class=\"Sentinel.Views.Gui.LogMessagesControl\"\n             x:Name=\"UserControl\"\n             d:DesignWidth=\"276.307\"\n             d:DesignHeight=\"34.038\">\n\n    <UserControl.Resources>\n        <Converters:TypeToSmallImageConverter x:Key=\"TypeToImageConverter\" />\n        <Converters:BooleanToWidthConverter x:Key=\"BooleanToWidthConverter\" />\n        <Converters:MessageHasExceptionMetadataConverter x:Key=\"MessageHasExceptionMetadata\" />\n        <Converters:MetaDataConverter x:Key=\"MetaDataConverter\" />\n        <Converters:MetaDataParameterConverter x:Key=\"MetaDataParameterConverter\" />\n\n        <Style TargetType=\"ListViewItem\">\n            <Setter Property=\"HorizontalContentAlignment\"\n                    Value=\"Stretch\" />\n        </Style>\n        <Converters:TimePreferenceConverter x:Key=\"TimePreferenceConverter\" />\n        <Converters:DatePreferenceConverter x:Key=\"DatePreferenceConverter\" />\n    </UserControl.Resources>\n\n    <Grid x:Name=\"LayoutRoot\">\n        <ListView x:Name=\"messages\"\n                  Grid.Column=\"0\"\n                  ScrollViewer.CanContentScroll=\"True\"\n                  ItemsSource=\"{Binding Messages}\">\n            <ListView.View>\n                <GridView>\n                    <Controls:FixedWidthColumn Header=\"Type\">\n                        <Controls:FixedWidthColumn.CellTemplate>\n                            <DataTemplate>\n                                <StackPanel Orientation=\"Horizontal\">\n                                    <Image x:Name=\"icon\"\n                                           Width=\"16\"\n                                           Height=\"16\"\n                                           RenderOptions.BitmapScalingMode=\"NearestNeighbor\"\n                                           VerticalAlignment=\"Center\"\n                                           HorizontalAlignment=\"Center\"\n                                           Source=\"{Binding Type, Converter={StaticResource TypeToImageConverter}}\"\n                                           Visibility=\"Collapsed\" />\n                                    <TextBlock x:Name=\"text\"\n                                               Text=\"{Binding Type}\"\n                                               Visibility=\"Collapsed\" />\n                                </StackPanel>\n                                <DataTemplate.Triggers>\n                                    <DataTrigger Binding=\"{Binding Preferences.SelectedTypeOption, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}}\"\n                                                 Value=\"1\">\n                                        <Setter TargetName=\"icon\"\n                                                Property=\"Visibility\"\n                                                Value=\"Visible\" />\n                                    </DataTrigger>\n                                    <DataTrigger Binding=\"{Binding Preferences.SelectedTypeOption, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}}\"\n                                                 Value=\"2\">\n                                        <Setter TargetName=\"text\"\n                                                Property=\"Visibility\"\n                                                Value=\"Visible\" />\n                                    </DataTrigger>\n                                    <DataTrigger Binding=\"{Binding Preferences.SelectedTypeOption, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}}\"\n                                                 Value=\"3\">\n                                        <Setter TargetName=\"icon\"\n                                                Property=\"Visibility\"\n                                                Value=\"Visible\" />\n                                        <Setter TargetName=\"text\"\n                                                Property=\"Visibility\"\n                                                Value=\"Visible\" />\n                                        <Setter TargetName=\"text\"\n                                                Property=\"Margin\"\n                                                Value=\"5,0,0,0\" />\n                                    </DataTrigger>\n                                </DataTemplate.Triggers>\n                            </DataTemplate>\n                        </Controls:FixedWidthColumn.CellTemplate>\n                    </Controls:FixedWidthColumn>\n                    <GridViewColumn Header=\"Date\" x:Name=\"DateColumn\"/>\n                    <GridViewColumn Header=\"Time\" x:Name=\"TimeColumn\"/>\n                    <Controls:FixedWidthColumn Header=\"Thread\"\n                                               FixedWidth=\"{Binding Preferences.ShowThreadColumn, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=50}\"\n                                               DisplayMemberBinding=\"{Binding Thread}\" />\n                    <GridViewColumn Header=\"Host\"\n                                    Width=\"{Binding Preferences.ShowSourceColumn, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=100}\"\n                                    DisplayMemberBinding=\"{Binding MetaData, Converter={StaticResource MetaDataConverter}, ConverterParameter='Host'}\" />\n                    <GridViewColumn Header=\"System\"\n                                    DisplayMemberBinding=\"{Binding System}\" />\n                    <Controls:FixedWidthColumn FixedWidth=\"{Binding Preferences.ShowExceptionColumn, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=30}\">\n                        <Controls:FixedWidthColumn.Header>\n                            <Image Width=\"12\"\n                                   Height=\"12\"\n                                   VerticalAlignment=\"Center\"\n                                   HorizontalAlignment=\"Center\"\n                                   RenderOptions.BitmapScalingMode=\"HighQuality\"\n                                   Source=\"../../Resources/Small/MonoLightning.png\" />\n                        </Controls:FixedWidthColumn.Header>\n                        <Controls:FixedWidthColumn.CellTemplate>\n                            <DataTemplate>\n                                <Image x:Name=\"icon\"\n                                       Width=\"16\"\n                                       Height=\"16\"\n                                       RenderOptions.BitmapScalingMode=\"NearestNeighbor\"\n                                       VerticalAlignment=\"Center\"\n                                       HorizontalAlignment=\"Center\"\n                                       Source=\"../../Resources/Small/Exception.png\"\n                                       Visibility=\"Hidden\" />\n                                <DataTemplate.Triggers>\n                                    <DataTrigger Binding=\"{Binding Converter={StaticResource MessageHasExceptionMetadata}}\"\n                                                 Value=\"true\">\n                                        <Setter TargetName=\"icon\"\n                                                Property=\"Visibility\"\n                                                Value=\"Visible\" />\n                                    </DataTrigger>\n                                </DataTemplate.Triggers>\n                            </DataTemplate>\n                        </Controls:FixedWidthColumn.CellTemplate>\n                    </Controls:FixedWidthColumn>\n                    <GridViewColumn Header=\"Description\"\n                                    DisplayMemberBinding=\"{Binding Description}\" />\n                    <GridViewColumn Header=\"Source File\"\n                                    Width=\"{Binding Preferences.ShowSourceInformationColumns, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=130}\"\n                                    DisplayMemberBinding=\"{Binding MetaData, Converter={StaticResource MetaDataConverter}, ConverterParameter='SourceFile'}\" />\n                    <GridViewColumn Width=\"{Binding Preferences.ShowSourceInformationColumns, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=30}\"\n                                    Header=\"Line\"\n                                    DisplayMemberBinding=\"{Binding MetaData, Converter={StaticResource MetaDataConverter}, ConverterParameter='SourceLine'}\" />\n                    <GridViewColumn Width=\"{Binding Preferences.ShowSourceInformationColumns, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=130}\"\n                                    Header=\"Class\"\n                                    DisplayMemberBinding=\"{Binding MetaData, Converter={StaticResource MetaDataConverter}, ConverterParameter='ClassName'}\" />\n                    <GridViewColumn Width=\"{Binding Preferences.ShowSourceInformationColumns, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=130}\"\n                                    Header=\"Method\"\n                                    DisplayMemberBinding=\"{Binding MetaData, Converter={StaticResource MetaDataConverter}, ConverterParameter='MethodName'}\" />\n                    <GridViewColumn Width=\"{Binding Preferences.ShowContextColumn, RelativeSource={RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}, Converter={StaticResource BooleanToWidthConverter}, ConverterParameter=300}\"\n                                    Header=\"Context\">\n                        <GridViewColumn.DisplayMemberBinding>\n                            <MultiBinding Converter=\"{StaticResource MetaDataParameterConverter}\">\n                                <Binding Path=\"MetaData\" />\n                                <Binding Path=\"Preferences.ContextProperty\"\n                                         RelativeSource=\"{RelativeSource AncestorType={x:Type Windows:MainWindow}, Mode=FindAncestor}\" />\n                            </MultiBinding>\n                            \n                        </GridViewColumn.DisplayMemberBinding>\n                    </GridViewColumn>\n\n                </GridView>\n            </ListView.View>\n        </ListView>\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/Views/Gui/LogMessagesControl.xaml.cs",
    "content": "﻿namespace Sentinel.Views.Gui\n{\n    using System;\n    using System.ComponentModel;\n    using System.Runtime.InteropServices;\n    using System.Text;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Data;\n    using System.Windows.Input;\n    using log4net;\n    using Sentinel.Highlighters;\n    using Sentinel.Highlighters.Interfaces;\n    using Sentinel.Interfaces;\n    using Sentinel.Interfaces.CodeContracts;\n\n    using Sentinel.Services;\n    using Sentinel.Support;\n    using Sentinel.Support.Wpf;\n\n    /// <summary>\n    /// Interaction logic for LogMessagesControl.xaml.\n    /// </summary>\n    public partial class LogMessagesControl : UserControl\n    {\n        private static readonly ILog Log = LogManager.GetLogger(typeof(LogMessagesControl));\n\n        public LogMessagesControl()\n        {\n            InitializeComponent();\n\n            AddCopyCommandBinding();\n\n            Highlight = ServiceLocator.Instance.Get<IHighlightingService<IHighlighter>>();\n\n            if (Highlight is INotifyPropertyChanged)\n            {\n                (Highlight as INotifyPropertyChanged).PropertyChanged += (s, e) => UpdateStyles();\n            }\n\n            var searchHighlighter = ServiceLocator.Instance.Get<ISearchHighlighter>();\n            if (searchHighlighter?.Highlighter is INotifyPropertyChanged)\n            {\n                ((INotifyPropertyChanged)searchHighlighter.Highlighter).PropertyChanged += (s, e) => UpdateStyles();\n            }\n\n            messages.ItemContainerStyleSelector = new HighlightingSelector(Messages_OnMouseDoubleClick);\n\n            Preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n            if (Preferences is INotifyPropertyChanged preferenceChanged)\n            {\n                preferenceChanged.PropertyChanged += PreferencesChanged;\n            }\n\n            // Read defaulted values from preferences\n            UpdateStyles();\n\n            UpdateDateFormat();\n            SetTypeColumnPreferences(Preferences?.SelectedTypeOption ?? 1);\n            DoubleClickToShowExceptions = Preferences != null && Preferences.DoubleClickToShowExceptions;\n        }\n\n        ~LogMessagesControl()\n        {\n            // Unregister observer of Preferences changing.\n            if (Preferences is INotifyPropertyChanged preferences)\n            {\n                preferences.PropertyChanged -= PreferencesChanged;\n            }\n        }\n\n        private IHighlightingService<IHighlighter> Highlight { get; }\n\n        private IUserPreferences Preferences { get; }\n\n        private bool DoubleClickToShowExceptions { get; set; }\n\n        public void ScrollToEnd()\n        {\n            ScrollingHelper.ScrollToEnd(Dispatcher, messages);\n        }\n\n        private void SetTypeColumnPreferences(int selectedTypeOption)\n        {\n            // TODO: to cope with resorting of columns, this code should search for the column, not assume it is the first.\n            // Get the first column in logDetails and check it is a fixed-width column.\n            var view = messages?.View as GridView;\n\n            if (view?.Columns[0] is FixedWidthColumn)\n            {\n                var fixedColumn = (FixedWidthColumn)view.Columns[0];\n                switch (selectedTypeOption)\n                {\n                    case 0:\n                        fixedColumn.FixedWidth = 0;\n                        break;\n                    case 1:\n                        fixedColumn.FixedWidth = 30;\n                        break;\n                    case 2:\n                        fixedColumn.FixedWidth = 60;\n                        break;\n                    case 3:\n                        fixedColumn.FixedWidth = 90;\n                        break;\n                }\n            }\n        }\n\n        private void UpdateDateFormat()\n        {\n            var view = messages?.View as GridView;\n\n            if (view != null)\n            {\n                // TODO: to cope with resorting of columns, this code should search for the column, not assume it is the second.\n                BindDateColumn(view.Columns[1]);\n                BindTimeColumn(view.Columns[2]);\n\n                // TODO: need to invalidate all existing ones!\n            }\n        }\n\n        private void BindTimeColumn(GridViewColumn column)\n        {\n            column.ThrowIfNull(nameof(column));\n\n            var converter = (IValueConverter)Resources[\"TimePreferenceConverter\"];\n            column.DisplayMemberBinding = new Binding(\".\") { Converter = converter, ConverterParameter = Preferences };\n        }\n\n        private void BindDateColumn(GridViewColumn column)\n        {\n            column.ThrowIfNull(nameof(column));\n\n            var converter = (IValueConverter)Resources[\"DatePreferenceConverter\"];\n            column.DisplayMemberBinding = new Binding(\".\") { Converter = converter, ConverterParameter = Preferences };\n        }\n\n        private void UpdateStyles()\n        {\n            messages.ItemContainerStyleSelector = null;\n            messages.ItemContainerStyleSelector = new HighlightingSelector(Messages_OnMouseDoubleClick);\n        }\n\n        private void Messages_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)\n        {\n            sender.ThrowIfNull(nameof(sender));\n\n            if (DoubleClickToShowExceptions)\n            {\n                if (sender is ListViewItem item)\n                {\n                    Log.Debug(\"Double click performed on entry\");\n\n                    if (item.HasContent)\n                    {\n                        if (item.Content is ILogEntry entry)\n                        {\n                            Log.Debug(entry.Type);\n                            Log.Debug(entry.Description);\n                        }\n                    }\n                }\n            }\n        }\n\n        private void CopySelectedLogEntries()\n        {\n            if (messages.SelectedItems.Count != 0)\n            {\n                var sb = new StringBuilder();\n                foreach (ILogEntry item in messages.SelectedItems)\n                {\n                    sb.AppendLine(\n                        $\"{item.DateTime.ToLocalTime():yyyy-MM-dd HH:mm:ss.ffff}|{item.Type}|{item.System}|{item.Description}\");\n                }\n\n                try\n                {\n                    Clipboard.SetData(DataFormats.Text, sb.ToString());\n                }\n                catch (Exception ex)\n                {\n                    throw new ApplicationException(\"Sentinel could not copy to the clipboard\", ex);\n                }\n            }\n        }\n\n        private void AddCopyCommandBinding()\n        {\n            void Handler(object s, ExecutedRoutedEventArgs a)\n            {\n                CopySelectedLogEntries();\n            }\n\n            var command = new RoutedCommand(\"Copy\", typeof(GridView));\n            command.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Control, \"Copy\"));\n            messages.CommandBindings.Add(new CommandBinding(command, Handler));\n\n            try\n            {\n                Clipboard.SetData(DataFormats.Text, string.Empty);\n            }\n            catch (COMException)\n            {\n            }\n        }\n\n        private void PreferencesChanged(object s, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"UseTighterRows\")\n            {\n                UpdateStyles();\n            }\n            else if (e.PropertyName == \"SelectedTypeOption\")\n            {\n                SetTypeColumnPreferences(Preferences.SelectedTypeOption);\n            }\n            else if (e.PropertyName == \"SelectedDateOption\")\n            {\n                UpdateDateFormat();\n            }\n            else if (e.PropertyName == \"DoubleClickToShowExceptions\")\n            {\n                DoubleClickToShowExceptions = Preferences.DoubleClickToShowExceptions;\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Gui/LogViewerToolbarButton.cs",
    "content": "﻿namespace Sentinel.Views.Gui\n{\n    using System.Diagnostics;\n    using System.Windows.Input;\n\n    using Sentinel.Views.Interfaces;\n\n    using WpfExtras;\n\n    public class LogViewerToolbarButton\n        : ViewModelBase, ILogViewerToolbarButton\n    {\n        private string imageIdentifier;\n\n        private bool isChecked;\n\n        public LogViewerToolbarButton(\n            string label,\n            string toolTip,\n            bool checkable,\n            ICommand command)\n        {\n            Tooltip = toolTip;\n            Label = label;\n            CanCheck = checkable;\n            Command = command;\n        }\n\n        public bool CanCheck { get; private set; }\n\n        public ICommand Command { get; private set; }\n\n        public string ImageIdentifier\n        {\n            get\n            {\n                return imageIdentifier;\n            }\n\n            set\n            {\n                if (imageIdentifier != value)\n                {\n                    imageIdentifier = value;\n                    OnPropertyChanged(nameof(ImageIdentifier));\n                }\n            }\n        }\n\n        public bool IsChecked\n        {\n            get\n            {\n                return isChecked;\n            }\n\n            set\n            {\n                Debug.Assert(CanCheck, \"Should not be able to check a non-checkable button, so why look?\");\n\n                if (isChecked != value)\n                {\n                    isChecked = value;\n                    OnPropertyChanged(nameof(IsChecked));\n                }\n            }\n        }\n\n        public string Label { get; private set; }\n\n        public string Tooltip { get; private set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Gui/MultipleViewFrame.xaml",
    "content": "﻿<UserControl xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:Wpf=\"clr-namespace:Sentinel.Support.Wpf\"\n             xmlns:Converters=\"clr-namespace:Sentinel.Support.Converters\"\n             mc:Ignorable=\"d\"\n             x:Class=\"Sentinel.Views.Gui.MultipleViewFrame\"\n             x:Name=\"UserControl\"\n             MinHeight=\"100\"\n             MinWidth=\"300\">\n\n    <UserControl.Resources>\n        <Style TargetType=\"{x:Type Image}\">\n            <Setter Property=\"RenderOptions.BitmapScalingMode\"\n                    Value=\"NearestNeighbor\" />\n        </Style>\n\n        <Style TargetType=\"{x:Type Image}\"\n               x:Key=\"toolbarCheckboxImageStyle\">\n            <Setter Property=\"RenderOptions.BitmapScalingMode\"\n                    Value=\"NearestNeighbor\" />\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding IsChecked, RelativeSource={RelativeSource AncestorLevel=1, AncestorType={x:Type CheckBox}}}\"\n                             Value=\"False\">\n                    <Setter Property=\"Opacity\"\n                            Value=\"0.50\" />\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n        <Converters:TypeToSmallImageConverter x:Key=\"typeToSmallImage\"/>\n        <Converters:ImageLibraryConverter x:Key=\"ImageLibrary\" />\n    </UserControl.Resources>\n\n    <Grid x:Name=\"LayoutRoot\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n\n        <ToolBarTray Grid.Row=\"0\"\n                     Grid.Column=\"0\"\n                     Grid.ColumnSpan=\"2\"\n                     x:Name=\"toolBarPanel1\">\n           \n            <Wpf:DataBoundToolbar x:Name=\"PrimaryToolbar\"\n                                  ItemsSource=\"{Binding PrimaryView.ToolbarItems}\"\n                                  Width=\"Auto\"\n                                  HorizontalContentAlignment=\"Center\">\n                <Wpf:DataBoundToolbar.ItemTemplate>\n                    <DataTemplate>\n                        <StackPanel Orientation=\"Horizontal\">\n                            <!-- Can be either a button or checkbox, define both and show/collapse on the DataTrigger -->\n                            <CheckBox x:Name=\"primaryViewCheckbox\"\n                                      Style=\"{DynamicResource {x:Static ToolBar.CheckBoxStyleKey}}\"\n                                      IsChecked=\"{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n                                      Command=\"{Binding Command}\"\n                                      Height=\"22\">\n                                <CheckBox.ToolTip>\n                                    <TextBlock Text=\"{Binding Tooltip}\" />\n                                </CheckBox.ToolTip>\n                                <StackPanel Orientation=\"Horizontal\">\n                                    <Image Source=\"{Binding ImageIdentifier, Converter={StaticResource ImageLibrary}}\"\n                                           Style=\"{StaticResource toolbarCheckboxImageStyle}\"\n                                           Width=\"16\"\n                                           Height=\"16\" />\n                                    <TextBlock Text=\"{Binding Label}\"\n                                               Margin=\"3,0\"\n                                               VerticalAlignment=\"Center\"\n                                               Width=\"Auto\" />\n                                </StackPanel>\n                            </CheckBox>\n                            \n                            <Button x:Name=\"primaryViewButton\"\n                                    Visibility=\"Collapsed\"\n                                    Style=\"{DynamicResource {x:Static ToolBar.ButtonStyleKey}}\"\n                                    Command=\"{Binding Command}\"\n                                    Height=\"22\">\n                                <Button.ToolTip>\n                                    <TextBlock Text=\"{Binding Tooltip}\" />\n                                </Button.ToolTip>\n                                <StackPanel Orientation=\"Horizontal\">\n                                    <Image Source=\"{Binding ImageIdentifier, Converter={StaticResource ImageLibrary}}\"\n                                           Width=\"16\"\n                                           Height=\"16\" />\n                                    <TextBlock Text=\"{Binding Label}\"\n                                               Margin=\"3,0\"\n                                               VerticalAlignment=\"Center\"\n                                               Width=\"Auto\" />\n                                </StackPanel>\n                            </Button>\n                        </StackPanel>\n                        \n                        <DataTemplate.Triggers>\n                            <DataTrigger Binding=\"{Binding CanCheck}\"\n                                         Value=\"false\">\n                                <Setter TargetName=\"primaryViewButton\"\n                                        Property=\"Visibility\"\n                                        Value=\"Visible\" />\n                                <Setter TargetName=\"primaryViewCheckbox\"\n                                        Property=\"Visibility\"\n                                        Value=\"Collapsed\" />\n                            </DataTrigger>\n                        </DataTemplate.Triggers>\n                        \n                    </DataTemplate>\n                </Wpf:DataBoundToolbar.ItemTemplate>\n            </Wpf:DataBoundToolbar>\n\n            <!--<ToolBar Header=\"Search : \"\n                     Width=\"Auto\"\n                     VerticalContentAlignment=\"Center\">\n                <TextBox Width=\"100\"\n                         Text=\"{Binding Search, UpdateSourceTrigger=PropertyChanged}\" />\n                <Label Content=\" in \"\n                       VerticalAlignment=\"Center\"\n                       Margin=\"5,0\"\n                       HorizontalAlignment=\"Center\" />\n                <ComboBox SelectedIndex=\"1\"\n                          IsEditable=\"False\"\n                          IsEnabled=\"False\"\n                          Width=\"75\">\n                    <ComboBox.Items>\n                        <ComboBoxItem Content=\"Type\"/>\n                        <ComboBoxItem Content=\"System\" />\n                        <ComboBoxItem Content=\"Description\" />\n                    </ComboBox.Items>\n                </ComboBox>                \n            </ToolBar>-->\n            \n        </ToolBarTray>\n\n        <DockPanel Grid.Row=\"1\">\n            <Grid>\n                <Grid.RowDefinitions>\n                    <RowDefinition Height=\"*\" />\n                    <RowDefinition Height=\"5\" />\n                    <RowDefinition Height=\"Auto\" />\n                </Grid.RowDefinitions>\n\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"*\" />\n                    <ColumnDefinition Width=\"5\" />\n                    <ColumnDefinition Width=\"Auto\" />\n                </Grid.ColumnDefinitions>\n\n                <DockPanel x:Name=\"first\"\n                           Grid.Row=\"0\"\n                           Grid.Column=\"0\"\n                           Grid.ColumnSpan=\"1\"\n                           Grid.RowSpan=\"3\">\n                    <Label DockPanel.Dock=\"Top\"\n                           Background=\"CornflowerBlue\"\n                           Content=\"{Binding PrimaryTitle}\"\n                           FontWeight=\"Bold\"\n                           Foreground=\"White\"\n                           FontSize=\"9\" />\n                    <ContentControl Content=\"{Binding PrimaryView.Presenter}\" />\n                </DockPanel>\n                <GridSplitter x:Name=\"splitter\"\n                              ResizeBehavior=\"PreviousAndNext\"\n                              Width=\"5\"\n                              Grid.Row=\"0\"\n                              Grid.Column=\"1\"\n                              Grid.RowSpan=\"3\"\n                              Grid.ColumnSpan=\"1\" />\n                <DockPanel x:Name=\"second\"\n                           Grid.Column=\"2\"\n                           Grid.ColumnSpan=\"1\"\n                           Grid.RowSpan=\"3\"\n                           Grid.Row=\"0\">\n                    <Label DockPanel.Dock=\"Top\"\n                           Background=\"CornflowerBlue\"\n                           Content=\"{Binding SecondaryTitle}\"\n                           FontWeight=\"Bold\"\n                           Foreground=\"White\"\n                           FontSize=\"9\" />\n                    <ContentControl Content=\"{Binding SecondaryView.Presenter}\" />\n                </DockPanel>\n            </Grid>\n        </DockPanel>\n        <TextBlock Grid.Row=\"4\"\n                   Grid.ColumnSpan=\"2\"\n                   Margin=\"0,2,0,0\"\n                   Text=\"{Binding PrimaryView.Status}\" />\n    </Grid>\n</UserControl>"
  },
  {
    "path": "Sentinel/Views/Gui/MultipleViewFrame.xaml.cs",
    "content": "﻿namespace Sentinel.Views.Gui\n{\n    using System;\n    using System.Collections.Generic;\n    using System.ComponentModel;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Input;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Services;\n    using Sentinel.Views.Interfaces;\n\n    /// <summary>\n    /// Interaction logic for MultipleViewFrame.xaml.\n    /// </summary>\n    public partial class MultipleViewFrame : INotifyPropertyChanged, IWindowFrame\n    {\n        private readonly IUserPreferences preferences = ServiceLocator.Instance.Get<IUserPreferences>();\n\n        // private readonly ISearchHighlighter searchHighlighter;\n        private readonly IViewManager viewManager = ServiceLocator.Instance.Get<IViewManager>();\n        private ILogger log;\n        private string primaryTitle;\n        private ILogViewer primaryView;\n        private string secondaryTitle;\n        private ILogViewer secondaryView;\n        private bool collapseSecondaryView;\n\n        public MultipleViewFrame()\n        {\n            InitializeComponent();\n\n            if (preferences is INotifyPropertyChanged)\n            {\n                (preferences as INotifyPropertyChanged).PropertyChanged += PreferencesChanged;\n            }\n\n            // Filters = ServiceLocator.Instance.Get<IFilteringService<IFilter>>();\n            // searchHighlighter = ServiceLocator.Instance.Get<ISearchHighlighter>();\n            SetupSplitter();\n\n            DataContext = this;\n        }\n\n        ~MultipleViewFrame()\n        {\n            var changed = preferences as INotifyPropertyChanged;\n            if (changed != null)\n            {\n                changed.PropertyChanged -= PreferencesChanged;\n            }\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public ICommand Clear { get; private set; }\n\n        public ICommand ClearActivity { get; private set; }\n\n        public ICommand Save { get; private set; }\n\n        public ILogViewer PrimaryView\n        {\n            get\n            {\n                return primaryView;\n            }\n\n            set\n            {\n                if (primaryView != value)\n                {\n                    primaryView = value;\n                    OnPropertyChanged(nameof(PrimaryView));\n                }\n            }\n        }\n\n        public string PrimaryTitle\n        {\n            get\n            {\n                return primaryTitle;\n            }\n\n            set\n            {\n                if (primaryTitle != value)\n                {\n                    primaryTitle = value;\n                    OnPropertyChanged(nameof(PrimaryTitle));\n                }\n            }\n        }\n\n        public string SecondaryTitle\n        {\n            get\n            {\n                return secondaryTitle;\n            }\n\n            set\n            {\n                if (secondaryTitle != value)\n                {\n                    secondaryTitle = value;\n                    OnPropertyChanged(nameof(SecondaryTitle));\n                }\n            }\n        }\n\n        public ILogViewer SecondaryView\n        {\n            get\n            {\n                return secondaryView;\n            }\n\n            set\n            {\n                if (secondaryView != value)\n                {\n                    secondaryView = value;\n                    OnPropertyChanged(nameof(SecondaryView));\n                }\n            }\n        }\n\n        public IUserPreferences Preferences => preferences;\n\n        public ILogger Log\n        {\n            get\n            {\n                return log;\n            }\n\n            set\n            {\n                if (log != value)\n                {\n                    log = value;\n                    OnPropertyChanged(nameof(Log));\n                }\n            }\n        }\n\n        public void SetViews(IEnumerable<string> viewIdentifiers)\n        {\n            var identifiers = viewIdentifiers as string[] ?? viewIdentifiers.ToArray();\n            if (identifiers.Any())\n            {\n                var guid = identifiers.ElementAt(0);\n                PrimaryView = viewManager.GetInstance(guid);\n                PrimaryView.SetLogger(log);\n                PrimaryTitle = viewManager.Get(guid).Name;\n            }\n\n            if (identifiers.Length >= 2)\n            {\n                var guid = identifiers.ElementAt(1);\n                SecondaryView = viewManager.GetInstance(guid);\n                SecondaryView.SetLogger(log);\n                SecondaryTitle = viewManager.Get(guid).Name;\n            }\n\n            if (identifiers.Length == 1)\n            {\n                CollapseSecondaryView();\n            }\n        }\n\n        protected void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void PreferencesChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"UseStackedLayout\")\n            {\n                SetupSplitter();\n            }\n        }\n\n        private void SetupSplitter()\n        {\n            if (!collapseSecondaryView)\n            {\n                var vertical = preferences.UseStackedLayout;\n\n                var rowSpan = vertical ? 1 : 3;\n                var colSpan = vertical ? 3 : 1;\n\n                Grid.SetRowSpan(first, rowSpan);\n                Grid.SetRowSpan(splitter, rowSpan);\n                Grid.SetRowSpan(second, rowSpan);\n\n                Grid.SetColumnSpan(first, colSpan);\n                Grid.SetColumnSpan(splitter, colSpan);\n                Grid.SetColumnSpan(second, colSpan);\n\n                splitter.Width = vertical ? int.MaxValue : 5;\n                splitter.Height = vertical ? 5 : int.MaxValue;\n                splitter.HorizontalAlignment = vertical\n                                                   ? HorizontalAlignment.Stretch\n                                                   : HorizontalAlignment.Left;\n                splitter.VerticalAlignment = vertical\n                                                 ? VerticalAlignment.Top\n                                                 : VerticalAlignment.Stretch;\n\n                Grid.SetColumn(first, 0);\n                Grid.SetRow(first, 0);\n\n                Grid.SetColumn(splitter, vertical ? 0 : 1);\n                Grid.SetRow(splitter, vertical ? 1 : 0);\n\n                Grid.SetColumn(second, vertical ? 0 : 2);\n                Grid.SetRow(second, vertical ? 2 : 0);\n\n                splitter.ResizeBehavior = GridResizeBehavior.PreviousAndNext;\n                splitter.ResizeDirection = vertical ? GridResizeDirection.Rows : GridResizeDirection.Columns;\n            }\n            else\n            {\n                Grid.SetRowSpan(first, 3);\n                Grid.SetColumnSpan(first, 3);\n\n                splitter.Visibility = Visibility.Hidden;\n                second.Visibility = Visibility.Hidden;\n            }\n        }\n\n        private void CollapseSecondaryView()\n        {\n            collapseSecondaryView = true;\n            SetupSplitter();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Gui/ViewInformation.cs",
    "content": "﻿namespace Sentinel.Views.Gui\n{\n    using Sentinel.Views.Interfaces;\n\n    public class ViewInformation : IViewInformation\n    {\n        public ViewInformation(string identifier, string name)\n        {\n            Identifier = identifier;\n            Name = name;\n        }\n\n        public string Identifier { get; private set; }\n\n        public string Name { get; private set; }\n\n        public string Description { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Heartbeat/HeartbeatControl.xaml",
    "content": "﻿<UserControl x:Class=\"Sentinel.Views.Heartbeat.HeartbeatControl\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             mc:Ignorable=\"d\"\n             d:DesignHeight=\"300\"\n             d:DesignWidth=\"300\"\n             SizeChanged=\"OnSizeChanged\"\n             Loaded=\"OnLoaded\">\n\n    <Grid>\n        <Canvas x:Name=\"canvas\"\n                Width=\"100\"\n                Height=\"100\"\n                ClipToBounds=\"True\"\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Center\" />\n\n        <!--\n        <ListView ItemsSource=\"{Binding Data}\" Grid.Column=\"1\">\n            <ListView.View>\n                <GridView>\n                    <GridView.Columns>\n                        <GridViewColumn Header=\"Type\"\n                                        DisplayMemberBinding=\"{Binding Key}\" />\n                        <GridViewColumn Header=\"Data\">\n                            <GridViewColumn.CellTemplate>\n                                <DataTemplate>\n                                    <ItemsControl ItemsSource=\"{Binding Value}\">\n                                        <ItemsControl.Template>\n                                            <ControlTemplate TargetType=\"ItemsControl\">\n                                                <ItemsPresenter />\n                                            </ControlTemplate>\n                                        </ItemsControl.Template>\n\n                                        <ItemsControl.ItemsPanel>\n                                            <ItemsPanelTemplate>\n                                                <StackPanel Orientation=\"Horizontal\" />\n                                            </ItemsPanelTemplate>\n                                        </ItemsControl.ItemsPanel>\n\n                                        <ItemsControl.ItemTemplate>\n                                            <DataTemplate>\n                                                <Border Width=\"30\"\n                                                        Margin=\"2\"\n                                                        BorderThickness=\"1\"\n                                                        CornerRadius=\"2\"\n                                                        BorderBrush=\"#FFF21616\">\n                                                    <TextBlock Text=\"{Binding}\"\n                                                               HorizontalAlignment=\"Center\" />\n                                                </Border>\n                                            </DataTemplate>\n                                        </ItemsControl.ItemTemplate>\n                                    </ItemsControl>\n                                </DataTemplate>\n                            </GridViewColumn.CellTemplate>\n                        </GridViewColumn>\n                    </GridView.Columns>\n                </GridView>\n            </ListView.View>\n        </ListView>\n        -->\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "Sentinel/Views/Heartbeat/HeartbeatControl.xaml.cs",
    "content": "﻿namespace Sentinel.Views.Heartbeat\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Media;\n    using System.Windows.Shapes;\n    using System.Windows.Threading;\n    using Sentinel.Support.Wpf;\n\n    /// <summary>\n    ///   Interaction logic for HeartbeatControl.xaml.\n    /// </summary>\n    public partial class HeartbeatControl : INotifyPropertyChanged\n    {\n        private ObservableDictionary<string, ObservableCollection<int>> data;\n\n        public HeartbeatControl()\n        {\n            InitializeComponent();\n            DataContext = this;\n\n            var samplePeriodTimer = new DispatcherTimer(DispatcherPriority.Normal)\n            {\n                Interval = TimeSpan.FromMilliseconds(1000),\n            };\n            samplePeriodTimer.Tick += SampleTick;\n            samplePeriodTimer.Start();\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public ObservableDictionary<string, ObservableCollection<int>> Data\n        {\n            get => data;\n            set\n            {\n                if (data != value)\n                {\n                    data = value;\n                    OnPropertyChanged(nameof(Data));\n                }\n            }\n        }\n\n        public IEnumerable<Point> CreatePoints(IEnumerable<int> values, int width, int height, double heightScale)\n        {\n            IList<Point> returnCollection = new List<Point>();\n\n            var stride = width / (values.Count() - 1);\n\n            var x = -stride;\n\n            // Populate structure with entry off to side and down to origin.\n            returnCollection.Add(new Point(x, height));\n            returnCollection.Add(new Point(x, height - (values.ElementAt(0) * heightScale)));\n            x += stride;\n\n            foreach (var value in values)\n            {\n                var y = (int)(value * heightScale);\n                returnCollection.Add(new Point(x, height - y));\n                x += stride;\n            }\n\n            // Post populate with values right off to the side (so null values don't enter visibility).\n            returnCollection.Add(new Point(x, height));\n\n            return returnCollection;\n        }\n\n        protected virtual void OnPropertyChanged(string propertyName)\n        {\n            PropertyChangedEventHandler handler = PropertyChanged;\n            if (handler != null)\n            {\n                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void SampleTick(object sender, EventArgs e)\n        {\n            // Dimensions of the canvas.\n            // Trace.WriteLine(string.Format(\"Canvas dimensions {0}x{1}\", canvas.Width, canvas.Height));\n\n            // Remove any polylines registered with the canvas.\n            canvas.Children.Clear();\n\n            if (Data != null)\n            {\n                int maxValue = Data.Count() > 0\n                    ? Data.Max(kvp => kvp.Value != null && kvp.Value.Count() > 0 ? kvp.Value.Max() : 0)\n                    : 0;\n                double scale = maxValue > 0 ? canvas.Height / maxValue : 1.0d;\n\n                foreach (KeyValuePair<string, ObservableCollection<int>> d in Data)\n                {\n                    var pc = new PointCollection(\n                        CreatePoints(\n                            d.Value,\n                            (int)canvas.Width,\n                            (int)canvas.Height,\n                            scale));\n                    var pl = new Polyline\n                    {\n                        Points = pc,\n                        StrokeThickness = 3.0d,\n                    };\n\n                    switch (d.Key)\n                    {\n                        case \"DEBUG\":\n                            pl.Stroke = Brushes.Green;\n                            break;\n                        case \"ERROR\":\n                            pl.Stroke = Brushes.Red;\n                            break;\n                        case \"INFO\":\n                            pl.Stroke = Brushes.Blue;\n                            break;\n                        case \"WARN\":\n                            pl.Stroke = Brushes.Yellow;\n                            break;\n                        case \"FATAL\":\n                            pl.Stroke = Brushes.Purple;\n                            break;\n                        default:\n                            pl.Stroke = Brushes.Black;\n                            break;\n                    }\n\n                    pl.Name = d.Key;\n                    canvas.Children.Add(pl);\n                }\n            }\n        }\n\n        private void OnSizeChanged(object sender, SizeChangedEventArgs e)\n        {\n            SetCanvasDimensions();\n        }\n\n        private void OnLoaded(object sender, RoutedEventArgs e)\n        {\n            SetCanvasDimensions();\n        }\n\n        private void SetCanvasDimensions()\n        {\n            canvas.Width = ActualWidth;\n            canvas.Height = ActualHeight;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Heartbeat/MessageHeatbeat.cs",
    "content": "﻿namespace Sentinel.Views.Heartbeat\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Linq;\n    using System.Windows.Controls;\n    using System.Windows.Threading;\n\n    using Sentinel.Interfaces;\n    using Sentinel.Support.Wpf;\n    using Sentinel.Views.Interfaces;\n\n    using WpfExtras;\n\n    public class MessageHeatBeat : ViewModelBase, ILogViewer\n    {\n        public static readonly string Id = \"f1da010a-bd8f-4957-a16d-2f3ada1e40f6\";\n\n        public static readonly IViewInformation Info = new ViewInformation(Id, \"Message Heartbeat\");\n\n        private const int MaxiumHistory = 200;\n\n        private const int SamplePeriod = 1000;\n\n        private readonly ObservableDictionary<string, ObservableCollection<int>> historicalData =\n            new ObservableDictionary<string, ObservableCollection<int>>();\n\n        private readonly Dictionary<string, int> liveData = new Dictionary<string, int>();\n\n        private HeartbeatControl presenter;\n\n        private ILogger logger;\n\n        public MessageHeatBeat()\n        {\n            ((ViewInformation)Info).Description = \"Displays a heartbeat graph based upon the incoming message type.\";\n\n            presenter = new HeartbeatControl { Data = historicalData };\n\n            // Register an interest in changes to self, so that when the caller changes\n            // properties on the view model, appropriate reactions can be preformed.\n            PropertyChanged += PropertyChangedHandler;\n\n            var samplePeriodTimer = new DispatcherTimer(DispatcherPriority.Normal)\n                                        {\n                                            Interval = TimeSpan.FromMilliseconds(SamplePeriod),\n                                        };\n            samplePeriodTimer.Tick += SampleTick;\n            samplePeriodTimer.Start();\n        }\n\n        public ObservableCollection<ILogEntry> Messages { get; private set; }\n\n        public string Name\n        {\n            get\n            {\n                return Info.Name;\n            }\n        }\n\n        public ILogger Logger\n        {\n            get\n            {\n                return logger;\n            }\n\n            private set\n            {\n                if (logger != value)\n                {\n                    if (logger != null)\n                    {\n                        logger.PropertyChanged -= LoggerPropertyChanged;\n                    }\n\n                    // If new logger isn't null, register to it.\n                    if (value != null)\n                    {\n                        value.PropertyChanged += LoggerPropertyChanged;\n                    }\n\n                    logger = value;\n                    OnPropertyChanged(nameof(Logger));\n                }\n\n                // TODO: Unregister from existing logger (if not null)\n            }\n        }\n\n        /// <summary>\n        /// Gets the Presenter control for a log viewer.\n        /// </summary>\n        public Control Presenter => presenter;\n\n        public string Status => string.Empty;\n\n        public IEnumerable<ILogViewerToolbarButton> ToolbarItems => null;\n\n        public ObservableDictionary<string, ObservableCollection<int>> Data => historicalData;\n\n        public void SetLogger(ILogger newLogger)\n        {\n            Logger = newLogger;\n        }\n\n        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"Logger\")\n            {\n                // Need to purge all historical/live data as it doesn't match the associated logger.\n                PurgeData();\n            }\n        }\n\n        private void LoggerPropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"NewEntries\")\n            {\n                lock (Logger.NewEntries)\n                {\n                    lock (liveData)\n                    {\n                        foreach (ILogEntry entry in Logger.NewEntries)\n                        {\n                            if (liveData.ContainsKey(entry.Type))\n                            {\n                                liveData[entry.Type]++;\n                            }\n                            else\n                            {\n                                liveData[entry.Type] = 1;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        private void SampleTick(object sender, EventArgs e)\n        {\n            lock (liveData)\n            {\n                lock (Data)\n                {\n                    // Push out old data.\n                    foreach (KeyValuePair<string, ObservableCollection<int>> pair in Data)\n                    {\n                        if (pair.Value.Count() >= MaxiumHistory)\n                        {\n                            pair.Value.RemoveAt(0);\n                        }\n\n                        // In cases where there is no liveData, need to set new value to zero\n                        if (!liveData.ContainsKey(pair.Key))\n                        {\n                            pair.Value.Add(0);\n                        }\n                    }\n\n                    // Push in new data\n                    foreach (var dataPoint in liveData)\n                    {\n                        if (!Data.ContainsKey(dataPoint.Key))\n                        {\n                            Data.Add(dataPoint.Key, new ObservableCollection<int>());\n                            for (int i = 0; i < MaxiumHistory - 1; i++)\n                            {\n                                Data[dataPoint.Key].Add(0);\n                            }\n                        }\n\n                        Data[dataPoint.Key].Add(dataPoint.Value);\n                    }\n                }\n\n                // Empty the collection\n                liveData.Clear();\n\n                OnPropertyChanged(nameof(Data));\n            }\n        }\n\n        private void PurgeData()\n        {\n            lock (Data)\n            {\n                Data.Clear();\n            }\n\n            lock (liveData)\n            {\n                liveData.Clear();\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Heartbeat/ViewInformation.cs",
    "content": "﻿namespace Sentinel.Views.Heartbeat\n{\n    public class ViewInformation : Sentinel.Views.Interfaces.IViewInformation\n    {\n        public ViewInformation(string identifier, string name)\n        {\n            Identifier = identifier;\n            Name = name;\n        }\n\n        public string Identifier { get; private set; }\n\n        public string Name { get; private set; }\n\n        public string Description { get; set; }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Interfaces/ILogViewer.cs",
    "content": "namespace Sentinel.Views.Interfaces\n{\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.Windows.Controls;\n\n    using Sentinel.Interfaces;\n\n    public interface ILogViewer\n    {\n        string Name { get; }\n\n        ILogger Logger { get; }\n\n        Control Presenter { get; }\n\n        ObservableCollection<ILogEntry> Messages { get; }\n\n        string Status { get; }\n\n        IEnumerable<ILogViewerToolbarButton> ToolbarItems { get; }\n\n        void SetLogger(ILogger newLogger);\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Interfaces/ILogViewerToolbarButton.cs",
    "content": "﻿namespace Sentinel.Views.Interfaces\n{\n    using System.Windows.Input;\n\n    public interface ILogViewerToolbarButton\n    {\n        string Tooltip { get; }\n\n        string Label { get; }\n\n        bool IsChecked { get; set; }\n\n        bool CanCheck { get; }\n\n        ICommand Command { get; }\n\n        string ImageIdentifier { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Interfaces/IViewInformation.cs",
    "content": "﻿namespace Sentinel.Views.Interfaces\n{\n    public interface IViewInformation\n    {\n        string Identifier { get; }\n\n        string Name { get; }\n\n        string Description { get; }\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Interfaces/IViewManager.cs",
    "content": "namespace Sentinel.Views.Interfaces\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n\n    public interface IViewManager\n    {\n        ObservableCollection<IWindowFrame> Viewers { get; }\n\n        IEnumerable<IViewInformation> Registered { get; }\n\n        void Register(IViewInformation info, Type viewerType);\n\n        IViewInformation Get(string identifier);\n\n        ILogViewer GetInstance(string identifier);\n    }\n}"
  },
  {
    "path": "Sentinel/Views/Interfaces/IWindowFrame.cs",
    "content": "﻿namespace Sentinel.Views.Interfaces\n{\n    using System.Collections.Generic;\n\n    using Sentinel.Interfaces;\n\n    public interface IWindowFrame\n    {\n        ILogger Log { get; set; }\n\n        ILogViewer PrimaryView { get; set; }\n\n        void SetViews(IEnumerable<string> viewIdentifiers);\n    }\n}"
  },
  {
    "path": "Sentinel/Views/ViewManager.cs",
    "content": "﻿namespace Sentinel.Views\n{\n    using System;\n    using System.Collections.Generic;\n    using System.Collections.ObjectModel;\n    using System.Diagnostics;\n    using System.Linq;\n\n    using Sentinel.Views.Gui;\n    using Sentinel.Views.Heartbeat;\n    using Sentinel.Views.Interfaces;\n\n    public class ViewManager : IViewManager\n    {\n        private readonly Dictionary<IViewInformation, Type> registeredTypes = new Dictionary<IViewInformation, Type>();\n\n        public ViewManager()\n        {\n            Register(LogMessages.Info, typeof(LogMessages));\n            Register(MessageHeatBeat.Info, typeof(MessageHeatBeat));\n        }\n\n        /// <summary>\n        /// Gets the observerable collection of the instances of a viewer main frame.\n        /// </summary>\n        public ObservableCollection<IWindowFrame> Viewers { get; } = new ObservableCollection<IWindowFrame>();\n\n        public IEnumerable<IViewInformation> Registered => registeredTypes.Keys;\n\n        public void Register(IViewInformation info, Type viewerType)\n        {\n            if (registeredTypes.Any(t => t.Key.Identifier == info.Identifier))\n            {\n                throw new NotSupportedException(\"Already have a registered viewer with the Id of \" + info.Identifier);\n            }\n\n            // Validate that the type supports the necessary interface: ILogViewer\n            var interfaceType = typeof(ILogViewer);\n            if (viewerType.GetInterfaces().All(i => i != interfaceType))\n            {\n                throw new NotSupportedException($\"Types registered in ViewManager must support the interface '{interfaceType}'\");\n            }\n\n            // Populate the registration information.\n            registeredTypes.Add(info, viewerType);\n        }\n\n        public IViewInformation Get(string identifier)\n        {\n            return registeredTypes.Keys.FirstOrDefault(v => v.Identifier == identifier);\n        }\n\n        public ILogViewer GetInstance(string identifier)\n        {\n            var registered = registeredTypes.Keys;\n\n            Debug.Assert(\n                registered.Concat(registered).Any(i => i.Identifier == identifier),\n                \"Identifier must be registered in the collection of views, either explicitly or by auto discovery\");\n\n            if (registered.Any(i => i.Identifier == identifier))\n            {\n                var t = registeredTypes.First(v => v.Key.Identifier == identifier).Value;\n\n                // Create an instance of the type (must have a default constructor).\n                return (ILogViewer)Activator.CreateInstance(t);\n            }\n\n            return null;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/BooleanInvertingValueConverter.cs",
    "content": "﻿namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows.Data;\n\n    public class BooleanInvertingValueConverter : IValueConverter\n    {\n        /// <summary>\n        /// Converts a value.\n        /// </summary>\n        /// <returns>\n        /// A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name=\"value\">The value produced by the binding source.</param>\n        /// <param name=\"targetType\">The type of the binding target property.</param>\n        /// <param name=\"parameter\">The converter parameter to use.</param>\n        /// <param name=\"culture\">The culture to use in the converter.</param>\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var original = (bool?)value;\n            return !original;\n        }\n\n        /// <summary>\n        /// Converts a value.\n        /// </summary>\n        /// <returns>\n        /// A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name=\"value\">The value that is produced by the binding target.</param>\n        /// <param name=\"targetType\">The type to convert to.</param>\n        /// <param name=\"parameter\">The converter parameter to use.</param>\n        /// <param name=\"culture\">The culture to use in the converter.</param>\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var original = (bool)value;\n            return !original;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/BooleanToDisabledConverter.cs",
    "content": "﻿namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows.Data;\n\n    public class BooleanToDisabledConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            return value == null || (value is bool && !(bool)value);\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            if (value != null)\n            {\n                return value is bool && !(bool)value;\n            }\n\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/CollapseIfFalseConverter.cs",
    "content": "namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows;\n    using System.Windows.Data;\n\n    public class CollapseIfFalseConverter : IValueConverter\n    {\n        /// <summary>\n        /// Converts a value.\n        /// </summary>\n        /// <returns>\n        /// A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name=\"value\">The value produced by the binding source.</param>\n        /// <param name=\"targetType\">The type of the binding target property.</param>\n        /// <param name=\"parameter\">The converter parameter to use.</param>\n        /// <param name=\"culture\">The culture to use in the converter.</param>\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            if (value is bool && targetType == typeof(Visibility))\n            {\n                return (bool)value ? Visibility.Visible : Visibility.Collapsed;\n            }\n\n            var valueAsBool = bool.Parse((string)value);\n            return valueAsBool ? Visibility.Visible : Visibility.Collapsed;\n        }\n\n        /// <summary>\n        /// Converts a value.\n        /// </summary>\n        /// <returns>\n        /// A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name=\"value\">The value that is produced by the binding target.</param>\n        /// <param name=\"targetType\">The type to convert to.</param>\n        /// <param name=\"parameter\">The converter parameter to use.</param>\n        /// <param name=\"culture\">The culture to use in the converter.</param>\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/CollapseIfZeroConverter.cs",
    "content": "namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows;\n    using System.Windows.Data;\n\n    public class CollapseIfZeroConverter : IValueConverter\n    {\n        /// <summary>\n        /// Converts a value.\n        /// </summary>\n        /// <returns>\n        /// A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name=\"value\">The value produced by the binding source.</param><param name=\"targetType\">The type of the binding target property.</param><param name=\"parameter\">The converter parameter to use.</param><param name=\"culture\">The culture to use in the converter.</param>\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            return (int)value == 0 ? Visibility.Collapsed : Visibility.Visible;\n        }\n\n        /// <summary>\n        /// Converts a value.\n        /// </summary>\n        /// <returns>\n        /// A converted value. If the method returns null, the valid null value is used.\n        /// </returns>\n        /// <param name=\"value\">The value that is produced by the binding target.</param><param name=\"targetType\">The type to convert to.</param><param name=\"parameter\">The converter parameter to use.</param><param name=\"culture\">The culture to use in the converter.</param>\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/ColourConverter.cs",
    "content": "﻿namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows;\n    using System.Windows.Data;\n    using System.Windows.Media;\n\n    [ValueConversion(typeof(Color), typeof(Brush))]\n    public class ColourConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            if (value == null)\n            {\n                if (parameter != null)\n                {\n                    switch (parameter.ToString())\n                    {\n                        case \"Window\":\n                            return SystemColors.WindowBrush;\n                        case \"WindowText\":\n                            return SystemColors.WindowTextBrush;\n                        default:\n                            return null;\n                    }\n                }\n\n                return null;\n            }\n\n            return new SolidColorBrush((Color)value);\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/VisibilityToCollapsedConverter.cs",
    "content": "﻿namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows;\n    using System.Windows.Data;\n\n    public class VisibilityToCollapsedConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var isVisible = (bool?)value ?? false;\n\n            // If a parameter is supplied, invert the condition.\n            if (parameter != null)\n            {\n                isVisible = !isVisible;\n            }\n\n            return isVisible ? Visibility.Visible : Visibility.Collapsed;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var isVisible = (bool)value;\n\n            // If a parameter is supplied, invert the condition.\n            if (parameter != null)\n            {\n                isVisible = !isVisible;\n            }\n\n            return isVisible ? Visibility.Collapsed : Visibility.Visible;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Converters/VisibilityToHiddenConverter.cs",
    "content": "namespace Sentinel.WpfExtras.Converters\n{\n    using System;\n    using System.Globalization;\n    using System.Windows;\n    using System.Windows.Data;\n\n    public class VisibilityToHiddenConverter : IValueConverter\n    {\n        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var isVisible = (bool)value;\n\n            if (parameter != null)\n            {\n                return isVisible ? Visibility.Hidden : Visibility.Visible;\n            }\n\n            return isVisible ? Visibility.Visible : Visibility.Hidden;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            var isVisible = (bool)value;\n            return isVisible ? Visibility.Hidden : Visibility.Visible;\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/DelegateCommand.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    using System;\n    using System.Windows.Input;\n\n    public class DelegateCommand : ICommand\n    {\n        public DelegateCommand(Action<object> executeAction, Predicate<object> canExecute = null)\n        {\n            ExecuteAction = executeAction;\n            CanExecutePredicate = canExecute;\n        }\n\n        public event EventHandler CanExecuteChanged\n        {\n            add\n            {\n                CommandManager.RequerySuggested += value;\n            }\n\n            remove\n            {\n                CommandManager.RequerySuggested -= value;\n            }\n        }\n\n        private Predicate<object> CanExecutePredicate { get; set; }\n\n        private Action<object> ExecuteAction { get; set; }\n\n        public bool CanExecute(object parameter)\n        {\n            return CanExecutePredicate == null || CanExecutePredicate.Invoke(parameter);\n        }\n\n        public void Execute(object parameter)\n        {\n            ExecuteAction.Invoke(parameter);\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/EstablishPossibleNavigation.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    public class EstablishPossibleNavigation\n    {\n        private readonly IWizardPage nodeToFind;\n\n        private readonly IWizardPage rootNode;\n\n        private bool found;\n\n        private IWizardPage previous;\n\n        public EstablishPossibleNavigation(IWizardPage rootNode, IWizardPage nodeToFind)\n        {\n            this.rootNode = rootNode;\n            this.nodeToFind = nodeToFind;\n        }\n\n        /// <summary>\n        /// Gets the first node located, should always be the original node upon which \"Accept\"\n        /// was called.\n        /// </summary>\n        public IWizardPage First { get; private set; }\n\n        /// <summary>\n        /// Gets last node found.\n        /// </summary>\n        public IWizardPage Last { get; private set; }\n\n        /// <summary>\n        /// Gets node immediately after found item.\n        /// </summary>\n        public IWizardPage Next { get; private set; }\n\n        /// <summary>\n        /// Node immediately before found item.\n        /// </summary>\n        public IWizardPage Previous => found ? previous : null;\n\n        public void Execute()\n        {\n            Visit(rootNode);\n        }\n\n        private void Visit(IWizardPage node)\n        {\n            if (First == null && node != rootNode)\n            {\n                First = node;\n            }\n\n            // Would update the Last node everytime, but if the only\n            // node is the root node, then this will be wrong.\n            if (node != rootNode)\n            {\n                Last = node;\n            }\n\n            if (!found)\n            {\n                if (node.Equals(nodeToFind))\n                {\n                    found = true;\n                }\n                else\n                {\n                    // Whilst not found, keep updating previous, last update\n                    // will be the one before the searched for node!\n                    if (node != rootNode)\n                    {\n                        previous = node;\n                    }\n                }\n            }\n            else\n            {\n                if (Next == null)\n                {\n                    Next = node;\n                }\n            }\n\n            foreach (var child in node.Children)\n            {\n                Visit(child);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/IWizardPage.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    using System.Collections.ObjectModel;\n    using System.ComponentModel;\n    using System.Windows.Controls;\n\n    public interface IWizardPage\n        : INotifyPropertyChanged\n    {\n        ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n        string Description { get; }\n\n        bool IsValid { get; }\n\n        Control PageContent { get; }\n\n        string Title { get; }\n\n        // Hierarchical Support.\n        void AddChild(IWizardPage child);\n\n        void RemoveChild(IWizardPage child);\n\n        object Save(object saveData);\n    }\n}\n"
  },
  {
    "path": "Sentinel/WpfExtras/PageChange.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    public enum PageChange\n    {\n        /// <summary>\n        /// First page\n        /// </summary>\n        First,\n\n        /// <summary>\n        /// Previous page\n        /// </summary>\n        Previous,\n\n        /// <summary>\n        /// Next page\n        /// </summary>\n        Next,\n\n        /// <summary>\n        /// Last page\n        /// </summary>\n        Last,\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/PageNavigationTreeEntry.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    using System.Collections.ObjectModel;\n    using System.Collections.Specialized;\n    using System.ComponentModel;\n    using System.Linq;\n\n    public class PageNavigationTreeEntry : INotifyPropertyChanged\n    {\n        private readonly ObservableCollection<PageNavigationTreeEntry> children;\n\n        private bool isCurrent;\n\n        public PageNavigationTreeEntry(IWizardPage page)\n        {\n            Page = page;\n\n            children = new ObservableCollection<PageNavigationTreeEntry>();\n            Children = new ReadOnlyObservableCollection<PageNavigationTreeEntry>(children);\n\n            page.PropertyChanged += PagePropertyChanged;\n            (page.Children as INotifyCollectionChanged).CollectionChanged += PageChildCollectionChanged;\n\n            foreach (var c in page.Children)\n            {\n                children.Add(new PageNavigationTreeEntry(c));\n            }\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public ReadOnlyObservableCollection<PageNavigationTreeEntry> Children { get; private set; }\n\n        public bool IsCurrent\n        {\n            get => isCurrent;\n\n            private set\n            {\n                if (isCurrent != value)\n                {\n                    isCurrent = value;\n                    OnPropertyChanged(nameof(IsCurrent));\n                }\n            }\n        }\n\n        public IWizardPage Page { get; private set; }\n\n        public void SetCurrentPage(IWizardPage wizardPage)\n        {\n            IsCurrent = wizardPage == Page;\n            foreach (var child in children)\n            {\n                child.SetCurrentPage(wizardPage);\n            }\n        }\n\n        private void OnPropertyChanged(string propertyName)\n        {\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        private void PageChildCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)\n        {\n            switch (e.Action)\n            {\n                case NotifyCollectionChangedAction.Add:\n                    foreach (var newItem in e.NewItems)\n                    {\n                        children.Add(new PageNavigationTreeEntry(newItem as IWizardPage));\n                    }\n\n                    break;\n                case NotifyCollectionChangedAction.Remove:\n                    var itemsToRemove = Children.Join(e.OldItems.OfType<IWizardPage>(), n => n.Page, p => p, (n, p) => n).ToList();\n                    foreach (var c in itemsToRemove)\n                    {\n                        children.Remove(c);\n                    }\n\n                    break;\n                case NotifyCollectionChangedAction.Reset:\n                    children.Clear();\n                    break;\n            }\n        }\n\n        private void PagePropertyChanged(object sender, PropertyChangedEventArgs e)\n        {\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/ViewModelBase.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    using System;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using log4net;\n\n    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable\n    {\n        private readonly ILog log;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"ViewModelBase\"/> class.\n        /// </summary>\n        protected ViewModelBase()\n        {\n            log = LogManager.GetLogger(GetType().Name);\n        }\n\n        /// <summary>\n        /// Finalizes an instance of the <see cref=\"ViewModelBase\"/> class.\n        /// </summary>\n        ~ViewModelBase()\n        {\n            log.DebugFormat(\"{0} ({1}) ({2}) Finalized\", GetType().Name, DisplayName, GetHashCode());\n        }\n\n        /// <summary>\n        /// Raised when a property on this object has a new value.\n        /// </summary>\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        /// <summary>\n        /// Gets or sets the display name for the view model, used for debug output.\n        /// </summary>\n        protected string DisplayName { get; set; }\n\n        /// <summary>\n        /// Gets a value indicating whether an exception is thrown, or if a Debug.Fail()\n        /// is used when an invalid property name is passed to the VerifyPropertyName method.\n        /// The default value is false, but subclasses used by unit tests might\n        /// override this property's getter to return true.\n        /// </summary>\n        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }\n\n        /// <summary>\n        /// Invoked when this object is being removed from the application\n        /// and will be subject to garbage collection.\n        /// </summary>\n        public void Dispose()\n        {\n            OnDispose();\n            GC.SuppressFinalize(this);\n        }\n\n        /// <summary>\n        /// Warns the developer if this object does not have\n        /// a public property with the specified name. This\n        /// method does not exist in a Release build.\n        /// </summary>\n        /// <param name=\"propertyName\">Name of property to investigate.</param>\n        [Conditional(\"DEBUG\")]\n        [DebuggerStepThrough]\n        public void VerifyPropertyName(string propertyName)\n        {\n            if (!string.IsNullOrEmpty(propertyName))\n            {\n                // Verify that the property name matches a real,\n                // public, instance property on this object.\n                if (TypeDescriptor.GetProperties(this)[propertyName] == null)\n                {\n                    var msg = \"Invalid property name: \" + propertyName;\n\n                    if (ThrowOnInvalidPropertyName)\n                    {\n                        throw new Exception(msg);\n                    }\n\n                    Debug.Fail(msg);\n                }\n            }\n        }\n\n        /// <summary>\n        /// Raises this object's PropertyChanged event.\n        /// </summary>\n        /// <param name=\"propertyName\">The property that has a new value.</param>\n        protected virtual void OnPropertyChanged(string propertyName)\n        {\n            VerifyPropertyName(propertyName);\n\n            var handler = PropertyChanged;\n            if (handler != null)\n            {\n                var e = new PropertyChangedEventArgs(propertyName);\n                handler(this, e);\n            }\n        }\n\n        /// <summary>\n        /// Child classes can override this method to perform\n        /// clean-up logic, such as removing event handlers.\n        /// </summary>\n        protected virtual void OnDispose()\n        {\n        }\n    }\n}"
  },
  {
    "path": "Sentinel/WpfExtras/Wizard.xaml",
    "content": "﻿<Window x:Class=\"Sentinel.WpfExtras.Wizard\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:converters=\"clr-namespace:Sentinel.WpfExtras.Converters\"\n        mc:Ignorable=\"d\"\n        MinHeight=\"377\"\n        MinWidth=\"500\"\n        Height=\"377\"\n        Width=\"500\"\n        Title=\"Wizard Title\"\n        WindowStyle=\"SingleBorderWindow\"\n        ResizeMode=\"CanResizeWithGrip\"\n        WindowStartupLocation=\"CenterOwner\"\n        Background=\"{DynamicResource {x:Static SystemColors.ControlBrushKey}}\"\n        ShowInTaskbar=\"False\">\n\n    <Window.Resources>\n        <converters:CollapseIfFalseConverter x:Key=\"CollapseIfFalse\" />\n        <HierarchicalDataTemplate x:Key=\"NavigationItemTemplate\"\n                                  ItemsSource=\"{Binding Children}\">\n            <TextBlock Text=\"{Binding Page.Title, UpdateSourceTrigger=PropertyChanged}\" />\n        </HierarchicalDataTemplate>\n        <Style x:Key=\"TreeViewStyle\"\n               TargetType=\"{x:Type TreeView}\">\n            <Setter Property=\"VerticalContentAlignment\"\n                    Value=\"Center\" />\n            <Setter Property=\"Background\"\n                    Value=\"{DynamicResource {x:Static SystemColors.WindowBrushKey}}\" />\n            <Setter Property=\"Template\">\n                <Setter.Value>\n                    <ControlTemplate TargetType=\"{x:Type TreeView}\">\n                        <ScrollViewer x:Name=\"_tv_scrollviewer_\"\n                                      Focusable=\"false\"\n                                      SnapsToDevicePixels=\"{TemplateBinding SnapsToDevicePixels}\"\n                                      CanContentScroll=\"false\"\n                                      HorizontalScrollBarVisibility=\"{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}\"\n                                      VerticalScrollBarVisibility=\"{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}\">\n                            <ItemsPresenter />\n                        </ScrollViewer>\n                    </ControlTemplate>\n                </Setter.Value>\n            </Setter>\n        </Style>\n\n        <Style x:Key=\"NavigationTreeViewContainer\"\n               TargetType=\"{x:Type TreeViewItem}\">\n            <Setter Property=\"Background\"\n                    Value=\"Transparent\" />\n            <Setter Property=\"HorizontalContentAlignment\"\n                    Value=\"{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}\" />\n            <Setter Property=\"VerticalContentAlignment\"\n                    Value=\"{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}\" />\n            <Setter Property=\"Foreground\"\n                    Value=\"{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}\" />\n            <Setter Property=\"FontWeight\"\n                    Value=\"Normal\" />\n            <Setter Property=\"Template\">\n                <Setter.Value>\n                    <ControlTemplate TargetType=\"{x:Type TreeViewItem}\">\n                        <Grid>\n                            <Grid.ColumnDefinitions>\n                                <ColumnDefinition MinWidth=\"10\"\n                                                  Width=\"Auto\" />\n                                <ColumnDefinition Width=\"Auto\" />\n                                <ColumnDefinition Width=\"*\" />\n                            </Grid.ColumnDefinitions>\n                            <Grid.RowDefinitions>\n                                <RowDefinition Height=\"25\" />\n                                <RowDefinition />\n                            </Grid.RowDefinitions>\n                            <Border x:Name=\"Bd\"\n                                    SnapsToDevicePixels=\"true\"\n                                    Grid.Column=\"1\"\n                                    Background=\"{TemplateBinding Background}\"\n                                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                                    Padding=\"{TemplateBinding Padding}\">\n                                <ContentPresenter x:Name=\"PART_Header\"\n                                                  VerticalAlignment=\"{TemplateBinding VerticalContentAlignment}\"\n                                                  HorizontalAlignment=\"{TemplateBinding HorizontalContentAlignment}\"\n                                                  SnapsToDevicePixels=\"{TemplateBinding SnapsToDevicePixels}\"\n                                                  ContentSource=\"Header\" />\n                            </Border>\n                            <ItemsPresenter x:Name=\"ItemsHost\"\n                                            Grid.Column=\"1\"\n                                            Grid.ColumnSpan=\"2\"\n                                            Grid.Row=\"1\" />\n                        </Grid>\n                    </ControlTemplate>\n                </Setter.Value>\n            </Setter>\n            <Style.Triggers>\n                <DataTrigger Binding=\"{Binding Page.IsValid}\"\n                             Value=\"False\">\n                    <Setter Property=\"Foreground\"\n                            Value=\"Red\" />\n                </DataTrigger>\n                <DataTrigger Binding=\"{Binding IsCurrent}\"\n                             Value=\"True\">\n                    <Setter Property=\"FontWeight\"\n                            Value=\"Bold\" />\n                </DataTrigger>\n            </Style.Triggers>\n        </Style>\n    </Window.Resources>\n\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"63\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"40\" />\n        </Grid.RowDefinitions>\n\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition Width=\"Auto\" />\n            <ColumnDefinition Width=\"*\" />\n        </Grid.ColumnDefinitions>\n\n        <TreeView Grid.Row=\"0\"\n                  Grid.Column=\"0\"\n                  Grid.RowSpan=\"2\"\n                  ItemsSource=\"{Binding NavigationTree}\"\n                  Visibility=\"{Binding ShowNavigationTree, Converter={StaticResource CollapseIfFalse}}\"\n                  ItemTemplate=\"{DynamicResource NavigationItemTemplate}\"\n                  ItemContainerStyle=\"{DynamicResource NavigationTreeViewContainer}\"\n                  Style=\"{DynamicResource TreeViewStyle}\"\n                  HorizontalAlignment=\"Stretch\"\n                  Width=\"150\"\n                  Focusable=\"False\" />\n\n        <DockPanel Background=\"{x:Static SystemColors.ControlLightLightBrush}\"\n                   Grid.Row=\"0\"\n                   Grid.Column=\"1\">\n            <StackPanel VerticalAlignment=\"Center\"\n                        Margin=\"10,0,10,0\"\n                        Orientation=\"Vertical\">\n                <TextBlock Text=\"{Binding CurrentPageContent.Title}\"\n                           Margin=\"0,0,0,5\"\n                           FontSize=\"16\"\n                           FontWeight=\"Bold\" />\n                <TextBlock Text=\"{Binding CurrentPageContent.Description}\"\n                           FontWeight=\"Bold\"\n                           TextWrapping=\"WrapWithOverflow\" />\n            </StackPanel>\n        </DockPanel>\n\n        <ContentPresenter x:Name=\"wizardPage\"\n                          Margin=\"5\"\n                          Grid.Row=\"1\"\n                          Grid.Column=\"1\"\n                          Content=\"{Binding CurrentPageContent}\"\n                          SnapsToDevicePixels=\"True\" />\n\n        <Line Grid.Row=\"2\"\n              Grid.ColumnSpan=\"2\"\n              SnapsToDevicePixels=\"True\"\n              Stretch=\"Fill\"\n              Stroke=\"{x:Static SystemColors.ControlDarkDarkBrush}\"\n              X1=\"0\"\n              X2=\"1\" />\n        <DockPanel Grid.Row=\"3\"\n                   Grid.ColumnSpan=\"2\"\n                   Margin=\"3\">\n            <StackPanel Orientation=\"Horizontal\"\n                        DockPanel.Dock=\"Right\">\n                <Button x:Name=\"backButton\"\n                        Width=\"75\"\n                        Height=\"23\"\n                        Margin=\"3,3,0,3\"\n                        Content=\"&lt; Back\"\n                        Command=\"{Binding Back}\"\n                        Loaded=\"OnLoaded\" />\n                <Button x:Name=\"nextButton\"\n                        Width=\"75\"\n                        Height=\"23\"\n                        Margin=\"0,3,3,3\"\n                        Content=\"Next &gt;\"\n                        Command=\"{Binding Forward}\" />\n                <Button x:Name=\"finishButton\"\n                        Width=\"75\"\n                        Height=\"23\"\n                        Margin=\"3\"\n                        Content=\"Finish\"\n                        Command=\"{Binding Finish}\" />\n                <Button x:Name=\"cancelButton\"\n                        Width=\"75\"\n                        Height=\"23\"\n                        Margin=\"3\"\n                        Content=\"Cancel\"\n                        Command=\"{Binding Cancel}\" />\n            </StackPanel>\n            <StackPanel VerticalAlignment=\"Center\">\n                <CheckBox Content=\"Show Navigation Tree\"\n                          IsChecked=\"{Binding ShowNavigationTree}\" />\n            </StackPanel>\n        </DockPanel>\n    </Grid>\n</Window>"
  },
  {
    "path": "Sentinel/WpfExtras/Wizard.xaml.cs",
    "content": "﻿namespace Sentinel.WpfExtras\n{\n    using System;\n    using System.Collections.ObjectModel;\n    using System.Collections.Specialized;\n    using System.ComponentModel;\n    using System.Diagnostics;\n    using System.Linq;\n    using System.Windows;\n    using System.Windows.Controls;\n    using System.Windows.Input;\n\n    /// <summary>\n    ///   Interaction logic for Wizard.xaml.\n    /// </summary>\n    public partial class Wizard\n    {\n        public static readonly DependencyProperty CurrentPageContentProperty = DependencyProperty.Register(\n            \"CurrentPageContent\",\n            typeof(Control),\n            typeof(Wizard),\n            new UIPropertyMetadata(null));\n\n        public static readonly DependencyProperty ShowNavigationTreeProperty = DependencyProperty.Register(\n            \"ShowNavigationTree\",\n            typeof(bool),\n            typeof(Wizard),\n            new UIPropertyMetadata(false));\n\n        public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(\n            \"ViewModel\",\n            typeof(INotifyPropertyChanged),\n            typeof(Wizard),\n            new UIPropertyMetadata(null));\n\n        private readonly PageNavigationTreeEntry navigationTree;\n\n        private readonly RootPage root = new RootPage();\n\n        private IWizardPage currentPage;\n\n        private IWizardPage first;\n\n        private IWizardPage last;\n\n        private IWizardPage next;\n\n        private IWizardPage previous;\n\n        public Wizard()\n        {\n            InitializeComponent();\n\n            navigationTree = new PageNavigationTreeEntry(root);\n\n            Back = new DelegateCommand(e => GoBack(), q => CanGoBack);\n            Forward = new DelegateCommand(e => GoForward(), q => CanGoForward);\n            Finish = new DelegateCommand(e => AcceptDialog(), q => CanFinish);\n            Cancel = new DelegateCommand(e => CancelDialog(), q => true);\n\n            // Register to root page that we are interested in changes.\n            root.PropertyChanged += PagesPropertyChangedHandler;\n\n            DataContext = this;\n        }\n\n        public ICommand Back { get; private set; }\n\n        public ICommand Cancel { get; private set; }\n\n        public Control CurrentPageContent\n        {\n            get\n            {\n                return (Control)GetValue(CurrentPageContentProperty);\n            }\n\n            set\n            {\n                SetValue(CurrentPageContentProperty, value);\n            }\n        }\n\n        public ICommand Finish { get; private set; }\n\n        public ICommand Forward { get; private set; }\n\n        public ReadOnlyObservableCollection<PageNavigationTreeEntry> NavigationTree => navigationTree.Children;\n\n        public object SavedData { get; set; }\n\n        public bool ShowNavigationTree\n        {\n            get\n            {\n                return (bool)GetValue(ShowNavigationTreeProperty);\n            }\n\n            set\n            {\n                SetValue(ShowNavigationTreeProperty, value);\n            }\n        }\n\n        public INotifyPropertyChanged ViewModel\n        {\n            get\n            {\n                return (INotifyPropertyChanged)GetValue(ViewModelProperty);\n            }\n\n            set\n            {\n                SetValue(ViewModelProperty, value);\n            }\n        }\n\n        private bool CanFinish => currentPage != null && currentPage.IsValid && currentPage == last;\n\n        private bool CanGoBack => currentPage != null && previous != null;\n\n        private bool CanGoForward => currentPage != null && currentPage.IsValid && next != null;\n\n        public void AddPage(IWizardPage page)\n        {\n            root.AddChild(page);\n        }\n\n        private void AcceptDialog()\n        {\n            if (currentPage != null)\n            {\n                SavedData = currentPage.Save(SavedData);\n            }\n\n            DialogResult = true;\n            Close();\n        }\n\n        private void CancelDialog()\n        {\n            DialogResult = false;\n            Close();\n        }\n\n        private void ChildObserver(object sender, PropertyChangedEventArgs e)\n        {\n            Debug.Assert(currentPage != null, \"Should not be reacting to child change if there is no current page!\");\n\n            switch (e.PropertyName)\n            {\n                case \"Children\":\n                    UpdateNavigationPossibilities();\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        private void GoBack()\n        {\n            Debug.Assert(CanGoBack, \"Should not be able to attempt to go back, the button's state should be disabled.\");\n\n            SwitchPage(PageChange.Previous);\n        }\n\n        private void GoForward()\n        {\n            Debug.Assert(CanGoForward, \"Should not be able to attempt to go forward, button should be disabled.\");\n\n            SwitchPage(PageChange.Next);\n        }\n\n        private void OnLoaded(object sender, RoutedEventArgs e)\n        {\n            Debug.Assert(root != null, \"The hidden root page should have been constructed.\");\n            Debug.Assert(root.Children != null, \"There must be at least one registered page\");\n            Debug.Assert(root.Children.Any(), \"There must be at least one registered page\");\n\n            SwitchPage(PageChange.First);\n        }\n\n        private void PagesPropertyChangedHandler(object sender, PropertyChangedEventArgs e)\n        {\n            if (e.PropertyName == \"Children\")\n            {\n                UpdateNavigationPossibilities();\n            }\n        }\n\n        private void SwitchPage(PageChange change)\n        {\n            IWizardPage newPage = null;\n\n            // If a current page exists, unbind from it.\n            if (currentPage != null)\n            {\n                currentPage.PropertyChanged -= ChildObserver;\n                (currentPage.Children as INotifyCollectionChanged).CollectionChanged -= WizardPageCollectionChanged;\n\n                switch (change)\n                {\n                    case PageChange.First:\n                        newPage = first;\n                        break;\n                    case PageChange.Previous:\n                        newPage = previous;\n                        break;\n                    case PageChange.Next:\n                        SavedData = currentPage.Save(SavedData);\n                        newPage = next;\n                        break;\n                    case PageChange.Last:\n                        SavedData = currentPage.Save(SavedData);\n                        newPage = last;\n                        break;\n                }\n            }\n            else\n            {\n                if (change == PageChange.Next || change == PageChange.Previous)\n                {\n                    throw new NotSupportedException(\"Unable to go to previous/next because no current page defined.\");\n                }\n\n                if (change == PageChange.First && first == null)\n                {\n                    throw new NotSupportedException(\"Unable to go to first page because no pages are registered.\");\n                }\n\n                if (change == PageChange.Last && last == null)\n                {\n                    throw new NotSupportedException(\"Unable to go to last page because no pages are registered.\");\n                }\n\n                newPage = change == PageChange.First ? first : last;\n            }\n\n            Debug.Assert(newPage != null, \"New page was a null reference, should not be the case!\");\n\n            // Register interest in changes within child view.\n            newPage.PropertyChanged += ChildObserver;\n            (newPage.Children as INotifyCollectionChanged).CollectionChanged += WizardPageCollectionChanged;\n\n            // Update current page.\n            CurrentPageContent = newPage.PageContent;\n            currentPage = newPage;\n            navigationTree.SetCurrentPage(currentPage);\n\n            UpdateNavigationPossibilities();\n        }\n\n        private void UpdateNavigationPossibilities()\n        {\n            var navPossibilities = new EstablishPossibleNavigation(root, currentPage);\n            navPossibilities.Execute();\n\n            first = navPossibilities.First;\n            last = navPossibilities.Last;\n            next = navPossibilities.Next;\n            previous = navPossibilities.Previous;\n        }\n\n        private void WizardPageCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)\n        {\n            UpdateNavigationPossibilities();\n        }\n\n        private sealed class RootPage : IWizardPage\n        {\n            private readonly ObservableCollection<IWizardPage> pages = new ObservableCollection<IWizardPage>();\n\n            public RootPage()\n            {\n                Children = new ReadOnlyObservableCollection<IWizardPage>(pages);\n            }\n\n            public event PropertyChangedEventHandler PropertyChanged;\n\n            public ReadOnlyObservableCollection<IWizardPage> Children { get; }\n\n            public string Description => \"Hidden Root Page - this page should never be shown\";\n\n            public bool IsValid => false;\n\n            public Control PageContent\n            {\n                get\n                {\n                    throw new NotImplementedException();\n                }\n            }\n\n            public string Title => \"Hidden Root Page\";\n\n            public void AddChild(IWizardPage newItem)\n            {\n                Debug.Assert(pages != null, \"Pages collection has not been constructed\");\n                pages.Add(newItem);\n                OnPropertyChanged(nameof(Children));\n            }\n\n            public void RemoveChild(IWizardPage item)\n            {\n                pages.Remove(item);\n                OnPropertyChanged(nameof(Children));\n            }\n\n            public object Save(object saveData)\n            {\n                return saveData;\n            }\n\n            /// <summary>\n            ///   Raises this object's PropertyChanged event.\n            /// </summary>\n            /// <param name = \"propertyName\">The property that has a new value.</param>\n            private void OnPropertyChanged(string propertyName)\n            {\n                var handler = PropertyChanged;\n                if (handler != null)\n                {\n                    var e = new PropertyChangedEventArgs(propertyName);\n                    handler(this, e);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Sentinel.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.30503.244\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"NLog2Tester\", \"NLog2Tester\\NLog2Tester.csproj\", \"{93081C58-433D-40D1-B0AD-524AA098D3B2}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Log4NetTester\", \"Log4NetTester\\Log4NetTester.csproj\", \"{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"nLog1Tester\", \"nLog1Tester\\nLog1Tester.csproj\", \"{44D1E267-AE99-43BE-B824-980B4ABFF9CA}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"NLog3Tester\", \"NLog3Tester\\NLog3Tester.csproj\", \"{26533F90-3677-4565-84AE-928BCFDC7DFB}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"nLog4Tester\", \"nLog4Tester\\nLog4Tester.csproj\", \"{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Setup\", \"Setup\", \"{5D931689-37BB-44D7-95AA-50A01E42567A}\"\n\tProjectSection(SolutionItemss) = preProject\n\t\tNew-Installer.ps1 = New-Installer.ps1\n\t\tSentinel.nuspec = Sentinel.nuspec\n\tEndProjectSection\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Testing Applications\", \"Testing Applications\", \"{D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"SerilogTester\", \"SerilogTester\\SerilogTester.csproj\", \"{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Sentinel\", \"Sentinel\\Sentinel.csproj\", \"{53824F40-064F-46B9-B063-1B6F7AB4CE95}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tStandalone_Debug|Any CPU = Standalone_Debug|Any CPU\n\t\tStandalone|Any CPU = Standalone|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Standalone_Debug|Any CPU.ActiveCfg = Standalone (Debug)|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Standalone_Debug|Any CPU.Build.0 = Standalone (Debug)|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2}.Standalone|Any CPU.Build.0 = Standalone|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Standalone_Debug|Any CPU.ActiveCfg = Standalone (Debug)|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Standalone_Debug|Any CPU.Build.0 = Standalone (Debug)|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC}.Standalone|Any CPU.Build.0 = Standalone|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Standalone_Debug|Any CPU.ActiveCfg = Standalone (Debug)|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Standalone_Debug|Any CPU.Build.0 = Standalone (Debug)|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA}.Standalone|Any CPU.Build.0 = Standalone|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Standalone_Debug|Any CPU.ActiveCfg = Standalone (Debug)|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Standalone_Debug|Any CPU.Build.0 = Standalone (Debug)|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB}.Standalone|Any CPU.Build.0 = Standalone|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Standalone_Debug|Any CPU.ActiveCfg = Standalone (Debug)|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Standalone_Debug|Any CPU.Build.0 = Standalone (Debug)|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7}.Standalone|Any CPU.Build.0 = Standalone|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Standalone_Debug|Any CPU.ActiveCfg = Standalone (Debug)|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Standalone_Debug|Any CPU.Build.0 = Standalone (Debug)|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37}.Standalone|Any CPU.Build.0 = Standalone|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Standalone_Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Standalone_Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{53824F40-064F-46B9-B063-1B6F7AB4CE95}.Standalone|Any CPU.Build.0 = Debug|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{93081C58-433D-40D1-B0AD-524AA098D3B2} = {D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\n\t\t{336279C2-07D0-4119-B02A-6AEE8ECAF4BC} = {D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\n\t\t{44D1E267-AE99-43BE-B824-980B4ABFF9CA} = {D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\n\t\t{26533F90-3677-4565-84AE-928BCFDC7DFB} = {D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\n\t\t{6E02A1CC-CFC2-48CD-95F5-3C0856AB9FF7} = {D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\n\t\t{A521BEE9-E9AA-49AA-9B9D-F98C4F95BD37} = {D85E5F34-4DD4-467C-A69B-C3312FDB6FBF}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {28BEB7F3-F084-4255-BB5B-82292544AAA3}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Sentinel.sln.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=UnnecessaryWhitespace/@EntryIndexedValue\">ERROR</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue\">None</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue\">NEVER</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue\">NEVER</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue\">NEVER</s:String>\n\t<s:Boolean x:Key=\"/Default/CodeStyle/CSharpUsing/PreferQualifiedReference/@EntryValue\">False</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=nlog/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/UserDictionary/Words/=Upgrader/@EntryIndexedValue\">True</s:Boolean></wpf:ResourceDictionary>"
  },
  {
    "path": "Settings.StyleCop",
    "content": "<StyleCopSettings Version=\"105\">\n  <Analyzers>\n    <Analyzer AnalyzerId=\"StyleCop.CSharp.ReadabilityRules\">\n      <Rules>\n        <Rule Name=\"PrefixCallsCorrectly\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"PrefixLocalCallsWithThis\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n      </Rules>\n      <AnalyzerSettings />\n    </Analyzer>\n    <Analyzer AnalyzerId=\"StyleCop.CSharp.DocumentationRules\">\n      <Rules>\n        <Rule Name=\"ElementsMustBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"PartialElementsMustBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"EnumerationItemsMustBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"DocumentationMustContainValidXml\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementDocumentationMustHaveSummary\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"PartialElementDocumentationMustHaveSummary\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementDocumentationMustHaveSummaryText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"PartialElementDocumentationMustHaveSummaryText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementDocumentationMustNotHaveDefaultSummary\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementParametersMustBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementParameterDocumentationMustMatchElementParameters\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementParameterDocumentationMustDeclareParameterName\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementParameterDocumentationMustHaveText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementReturnValueMustBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementReturnValueDocumentationMustHaveText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"VoidReturnValueMustNotBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"GenericTypeParametersMustBeDocumented\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"GenericTypeParametersMustBeDocumentedPartialClass\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"GenericTypeParameterDocumentationMustMatchTypeParameters\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"GenericTypeParameterDocumentationMustDeclareParameterName\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"GenericTypeParameterDocumentationMustHaveText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"PropertySummaryDocumentationMustMatchAccessors\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"PropertySummaryDocumentationMustOmitSetAccessorWithRestrictedAccess\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementDocumentationMustNotBeCopiedAndPasted\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"SingleLineCommentsMustNotUseDocumentationStyleSlashes\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"DocumentationTextMustNotBeEmpty\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"DocumentationTextMustContainWhitespace\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"DocumentationMustMeetCharacterPercentage\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ConstructorSummaryDocumentationMustBeginWithStandardText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"DestructorSummaryDocumentationMustBeginWithStandardText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"DocumentationHeadersMustNotContainBlankLines\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"IncludedDocumentationXPathDoesNotExist\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"IncludeNodeDoesNotContainValidFileAndPath\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"InheritDocMustBeUsedWithInheritingClass\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"ElementDocumentationMustBeSpelledCorrectly\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileMustHaveHeader\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileHeaderMustShowCopyright\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileHeaderMustHaveCopyrightText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileHeaderMustContainFileName\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileHeaderFileNameDocumentationMustMatchFileName\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileHeaderMustHaveValidCompanyText\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n        <Rule Name=\"FileHeaderFileNameDocumentationMustMatchTypeName\">\n          <RuleSettings>\n            <BooleanProperty Name=\"Enabled\">False</BooleanProperty>\n          </RuleSettings>\n        </Rule>\n      </Rules>\n      <AnalyzerSettings />\n    </Analyzer>\n  </Analyzers>\n</StyleCopSettings>"
  },
  {
    "path": "SharedAssemblyInfo.cs",
    "content": "﻿using System;\nusing System.Reflection;\nusing System.Resources;\n\n[assembly: AssemblyVersion(\"0.14.2.0\")]\n[assembly: AssemblyFileVersion(\"0.14.2.0\")]\n\n// Ensure all assemblies have a neutral language defined.\n[assembly: NeutralResourcesLanguage(\"en\")]\n\n// Address CA1014 issues\n[assembly: CLSCompliant(true)]\n\n[assembly: AssemblyCopyright(\"Copyright © Ray Hayes 2009-2020\")]\n\n[assembly: AssemblyInformationalVersion(\"0.14.2-UpdateAllFrameWorks.1+7.Branch.UpdateAllFrameWorks.Sha.8a94b1206e6fb581a78b616fb62cef16447a9b07\")]\n"
  },
  {
    "path": "StyleCop/StyleCopRules.ruleset",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RuleSet Name=\"Rules for StyleCop.Analyzers\" Description=\"Code analysis rules for StyleCop.Analyzers.csproj.\" ToolsVersion=\"16.0\">\n  <Rules AnalyzerId=\"AsyncUsageAnalyzers\" RuleNamespace=\"AsyncUsageAnalyzers\">\n    <Rule Id=\"UseConfigureAwait\" Action=\"Warning\" />\n  </Rules>\n  <Rules AnalyzerId=\"Microsoft.Analyzers.ManagedCodeAnalysis\" RuleNamespace=\"Microsoft.Rules.Managed\">\n    <Rule Id=\"CA1001\" Action=\"Warning\" />\n    <Rule Id=\"CA1009\" Action=\"Warning\" />\n    <Rule Id=\"CA1016\" Action=\"Warning\" />\n    <Rule Id=\"CA1033\" Action=\"Warning\" />\n    <Rule Id=\"CA1049\" Action=\"Warning\" />\n    <Rule Id=\"CA1060\" Action=\"Warning\" />\n    <Rule Id=\"CA1061\" Action=\"Warning\" />\n    <Rule Id=\"CA1063\" Action=\"Warning\" />\n    <Rule Id=\"CA1065\" Action=\"Warning\" />\n    <Rule Id=\"CA1301\" Action=\"Warning\" />\n    <Rule Id=\"CA1400\" Action=\"Warning\" />\n    <Rule Id=\"CA1401\" Action=\"Warning\" />\n    <Rule Id=\"CA1403\" Action=\"Warning\" />\n    <Rule Id=\"CA1404\" Action=\"Warning\" />\n    <Rule Id=\"CA1405\" Action=\"Warning\" />\n    <Rule Id=\"CA1410\" Action=\"Warning\" />\n    <Rule Id=\"CA1415\" Action=\"Warning\" />\n    <Rule Id=\"CA1821\" Action=\"Warning\" />\n    <Rule Id=\"CA1900\" Action=\"Warning\" />\n    <Rule Id=\"CA1901\" Action=\"Warning\" />\n    <Rule Id=\"CA2002\" Action=\"Warning\" />\n    <Rule Id=\"CA2100\" Action=\"Warning\" />\n    <Rule Id=\"CA2101\" Action=\"Warning\" />\n    <Rule Id=\"CA2108\" Action=\"Warning\" />\n    <Rule Id=\"CA2111\" Action=\"Warning\" />\n    <Rule Id=\"CA2112\" Action=\"Warning\" />\n    <Rule Id=\"CA2114\" Action=\"Warning\" />\n    <Rule Id=\"CA2116\" Action=\"Warning\" />\n    <Rule Id=\"CA2117\" Action=\"Warning\" />\n    <Rule Id=\"CA2122\" Action=\"Warning\" />\n    <Rule Id=\"CA2123\" Action=\"Warning\" />\n    <Rule Id=\"CA2124\" Action=\"Warning\" />\n    <Rule Id=\"CA2126\" Action=\"Warning\" />\n    <Rule Id=\"CA2131\" Action=\"Warning\" />\n    <Rule Id=\"CA2132\" Action=\"Warning\" />\n    <Rule Id=\"CA2133\" Action=\"Warning\" />\n    <Rule Id=\"CA2134\" Action=\"Warning\" />\n    <Rule Id=\"CA2137\" Action=\"Warning\" />\n    <Rule Id=\"CA2138\" Action=\"Warning\" />\n    <Rule Id=\"CA2140\" Action=\"Warning\" />\n    <Rule Id=\"CA2141\" Action=\"Warning\" />\n    <Rule Id=\"CA2146\" Action=\"Warning\" />\n    <Rule Id=\"CA2147\" Action=\"Warning\" />\n    <Rule Id=\"CA2149\" Action=\"Warning\" />\n    <Rule Id=\"CA2200\" Action=\"Warning\" />\n    <Rule Id=\"CA2202\" Action=\"Warning\" />\n    <Rule Id=\"CA2207\" Action=\"Warning\" />\n    <Rule Id=\"CA2212\" Action=\"Warning\" />\n    <Rule Id=\"CA2213\" Action=\"Warning\" />\n    <Rule Id=\"CA2214\" Action=\"Warning\" />\n    <Rule Id=\"CA2216\" Action=\"Warning\" />\n    <Rule Id=\"CA2220\" Action=\"Warning\" />\n    <Rule Id=\"CA2229\" Action=\"Warning\" />\n    <Rule Id=\"CA2231\" Action=\"Warning\" />\n    <Rule Id=\"CA2232\" Action=\"Warning\" />\n    <Rule Id=\"CA2235\" Action=\"Warning\" />\n    <Rule Id=\"CA2236\" Action=\"Warning\" />\n    <Rule Id=\"CA2237\" Action=\"Warning\" />\n    <Rule Id=\"CA2238\" Action=\"Warning\" />\n    <Rule Id=\"CA2240\" Action=\"Warning\" />\n    <Rule Id=\"CA2241\" Action=\"Warning\" />\n    <Rule Id=\"CA2242\" Action=\"Warning\" />\n  </Rules>\n  <Rules AnalyzerId=\"Microsoft.CodeAnalysis.CSharp\" RuleNamespace=\"Microsoft.CodeAnalysis.CSharp\">\n    <Rule Id=\"CS1573\" Action=\"None\" />\n    <Rule Id=\"CS1591\" Action=\"None\" />\n  </Rules>\n  <Rules AnalyzerId=\"Microsoft.CodeAnalysis.CSharp.Features\" RuleNamespace=\"Microsoft.CodeAnalysis.CSharp.Features\">\n    <Rule Id=\"IDE0003\" Action=\"None\" />\n  </Rules>\n  <Rules AnalyzerId=\"StyleCop.Analyzers\" RuleNamespace=\"StyleCop.Analyzers\">\n    <Rule Id=\"SA0001\" Action=\"None\" />\n    <Rule Id=\"SA1101\" Action=\"None\" />\n    <Rule Id=\"SA1305\" Action=\"Warning\" />\n    <Rule Id=\"SA1600\" Action=\"None\" />\n    <Rule Id=\"SA1601\" Action=\"None\" />\n    <Rule Id=\"SA1623\" Action=\"None\" />\n    <Rule Id=\"SA1633\" Action=\"None\" />\n    <Rule Id=\"SA1652\" Action=\"None\" />\n  </Rules>\n</RuleSet>"
  },
  {
    "path": "docs/Home.md",
    "content": "# ![](Debug.png) sentinel\r\nLog-viewer with filtering and highlighting\r\n\r\n![](Home_sentinel-0.11.0.0.png)\r\n\r\n## Log Sources \r\nSentinel is a viewer for log-files - specifically I designed it to act as a network end-point for the likes of nLog and log4net, additionally it then works really well for capturing log entries from multiple sources.  \r\n\r\nSource                | Status\r\n--------------------- | ------\r\nLog4Net UdpAppender   | Supported \r\nnLog's nLogViewer     | Supported\r\nTrace Listener        | Planned\r\nLog-File Watcher      | Experimental\r\nCustom, via plug-in   | Planned\r\nMSBuild               | Plug-in in source-repo\r\n\r\n## Command-Line usage\r\nThere are command line options that allow control over Sentinel when started, options available include the following:\r\n* Loading of a saved Session File\r\n* nLog network listener\r\n* log4net network listener\r\n\r\n## Command line options\r\nIf no command line options are specified, the standard New Session wizard will launch at start-up.\r\n\r\n### Launch with NLog listener enabled\r\n```\r\nsentinel nlog [-p portNumber](-p-portNumber) [-udp](-tcp)\r\n```\r\nDefaults to port 9999 and Udp if not specified.\r\n\r\n### Launch with Log4Net listener enabled\r\n```\r\nsentinel log4net [-p portNumber](-p-portNumber) \r\n```\r\nDefaults to port 9998 if not specified.\r\n\r\n### Launch with previously saved session file\r\n```\r\nsentinel filename.SNTL\r\n```\r\n\r\n## nLog's NLogViewer target configuration\r\nTo allow a nLog based application transmit its log messages to Sentinel, use a configuration like the one shown below:\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<nlog xmlns=\"http://www.nlog-project.org/schemas/NLog.xsd\"\r\n      xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\r\n  <targets>\r\n    <target xsi:type=\"NLogViewer\"\r\n            name=\"viewer\"\r\n            address=\"udp://127.0.0.1:9999\"/>\r\n  </targets>\r\n  <rules>\r\n    <logger name=\"*\"\r\n            minlevel=\"Debug\"\r\n            writeTo=\"viewer\" />\r\n  </rules>\r\n</nlog>\r\n```\r\n\r\n### Showing nLog debug information (0.12.0.0 onwards)\r\nIf the above configuration is adjusted to enable {{includeSourceInfo}} then it is possible to see the file, class, method and line number corresponding to where the message is emitted.  Some of this information is only reported if the source program is compiled in DEBUG mode (e.g. RELEASE mode strips this information)\r\n```xml\r\n<target name=\"viewer\"\r\n        xsi:type=\"NLogViewer\"\r\n        includeSourceInfo=\"true\"\r\n        address=\"udp://127.0.0.1:9999\" />\r\n```\r\n\r\n## Log4Net UdpAppender configuration\r\nTo allow a log4net application transmit its log messages to Sentinel, use a configuration like the one shown below:\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<configuration>\r\n  <configSections>\r\n    <section name=\"log4net\"\r\n             type=\"log4net.Config.Log4NetConfigurationSectionHandler, log4net\"/>\r\n  </configSections>\r\n\r\n  <log4net>\r\n    <appender name=\"udp\"\r\n              type=\"log4net.Appender.UdpAppender\">\r\n\r\n      <RemoteAddress value=\"127.0.0.2\"/>\r\n      <RemotePort value=\"9999\"/>\r\n      <layout type=\"log4net.Layout.XmlLayout\"/>\r\n    </appender>\r\n\r\n    <root>\r\n      <appender-ref ref=\"udp\"/>\r\n    </root>\r\n  </log4net>\r\n</configuration>\r\n```\r\n### Showing log4net debug information (0.12.1.0 onwards)\r\nIf the above configuration is adjusted to enable {{locationInfo}} then it is possible to see the file, class, method and line number corresponding to where the message is emitted.  Some of this information is only reported if the source program is compiled in DEBUG mode (e.g. RELEASE mode strips this information)\r\n```xml\r\n<layout type=\"log4net.Layout.XmlLayout\">\r\n    <locationInfo value=\"true\" />\r\n</layout>\r\n```\r\n\r\n## Log Entries\r\nLog file entries map the the following interface.  This is core record for a log-file entry and used to populate the columns within the live-log view.  Note, by using [Classifiers](#Classifiers) it is possible to rearrange and/or change the content of these fields upon receiving a new log-entry.\r\n\r\n```c#\r\npublic interface ILogEntry\r\n{\r\n    string Classification { get; set; }\r\n    DateTime DateTime { get; set; }\r\n    string Description { get; set; }\r\n    string Source { get; set; }\r\n    string System { get; set; }\r\n    string Thread { get; set; }\r\n    string Type { get; set; }\r\n}\r\n```\r\n\r\nLog entries may be classified, highlighted and filtered based upon special services:\r\n* Classifiers can change the properties of a log entry\r\n* Highlighters can change its appearance.\r\n* Filters can be used to suppress the displaying of matching entries.\r\n\r\n## Classifiers\r\nUpon receiving a new log-entry it is processed through registered classifiers.  Classifiers have the ability to rewrite the log-entry prior to passing it to the visualisation aspect of Sentinel. \r\n\r\nAs an example, suppose the incoming message when starting with the phrase \"Sleeping for another\" is intended to be switched from its supplied type (e.g. DEBUG or INFO) to its own type **Timing**.  The ClassifierViewModel registers this on construction.  Note that the regular expression below also rewrites the Description to the named-capture _description_, effectively stripping off the prefix \"Sleeping for another\" - information no longer needed due to the reclassification.\r\n\r\n```c#\r\nitems.Add(\r\n\tnew DescriptionTypeClassifier(\"Timing\", \"Timing\")\r\n\t\t{\r\n\t\t\tEnabled = true,\r\n\t\t\tName = \"Timing messages\",\r\n\t\t\tRegexString = @\"^Sleeping for another (?<description>[^$](^$)+)$\"\r\n\t\t});\r\n```\r\n\r\nIn addition to reclassifying messages, the Classifier mechanism is currently used to make other changes to the message appearance.  Continuing the example above, using a {{TypeImageClassifier}} it is possible to specify the image to be used for entries with a type of **Timing**.\r\n\r\n```c#\r\nitems.Add(\r\n\tnew TypeImageClassifier(\"Timing\", \"/Resources/Clock.png\")\r\n\t\t{\r\n\t\t\tEnabled = true,\r\n\t\t\tName = \"Timing Image\",\r\n\t\t});\r\n```\r\nThe classifiers can be seen in the Preferences dialog-box.\r\n\r\n![Preferences - Classifiers](Home_Preferences-Classifiers.png)\r\n## Highlighters\r\nCustomisable and extendible highlighters that may be toggled on and off during live preview.  The current implementation limits pattern matching to the Type and System fields, although this will be extended to all fields.  An example of why this is useful, in the classifiers section above, a new type of \"Timing\" was added, this type can get its own highlighting style.  \r\n\r\n![Preferences - Highlighters](Home_Preferences-Highlighters.png)\r\n\r\nHighlighters can match the contents of the Type and System fields\r\n* Exact Strings\r\n* Substrings\r\n* Regular Expressions\r\n\r\n![Adding Regex Highlighter](Home_Add%20Highlighter%20-%20Regex.png)\r\n\r\nIf the matching field is set to **Type** an the match string specified as \"Timing\", a new highlighter for the timing can be added.  User-defined highlighters are automatically added to the toolbar for ease of enabling and disabling of the highlighting.  \r\n\r\n![Toolbar - User defined highlighters](Home_Toolbar%20-%20User%20defined%20highlighter.png)\r\n\r\nThe highlighters work on a first-come, wins principle.  Therefore, the order of the entries in the highlighters section of the Preferences dialog-box are important.  It is possible to hide the highlighting of  FATAL messages if a highlighter is positioned before FATAL and gets a match.\r\n\r\n## Filters\r\nFilters are very much like highlighters except their purpose is to remove log-entries from the displayed values (note, the values are not lost, just hidden).  Filters may be toggled on and off during the session.  Filters are evaluated in the order specified, but since filters works on an any-match = hide principle the evaluation stops on the first match, resulting on the entry being hidden.  Note, this means that messages displayed have travelled through ALL of the filters without being matched, this isn't a cost free aspect, so be careful about how many enabled filters you have!\r\n\r\n## Extractors\r\nExtractors are the inverse of Filters, log entries must match the extractor to be visible.  This can be very useful if you, for example, define an extractor for a specific logging condition and want to quickly see if and how often it happened.\r\n"
  }
]