diff --git a/Tailviewer.sln b/Tailviewer.sln
index 02e1aa890..d8b14c5fa 100644
--- a/Tailviewer.sln
+++ b/Tailviewer.sln
@@ -30,6 +30,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Installer", "src\Installer\
{9997F434-58B2-4C26-B622-5CA411A82BF7} = {9997F434-58B2-4C26-B622-5CA411A82BF7}
{C667EB8A-6781-4623-AC7C-D3FBA2F6E7D4} = {C667EB8A-6781-4623-AC7C-D3FBA2F6E7D4}
{799F74C7-6DE8-455D-936D-E11D7525AB36} = {799F74C7-6DE8-455D-936D-E11D7525AB36}
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22} = {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tailviewer.Api", "src\Tailviewer.Api\Tailviewer.Api.csproj", "{0C18B216-9FF4-4DCF-88C0-4B1448892F43}"
@@ -64,6 +65,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tailviewer.Formats.Serilog"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tailviewer.Formats.Serilog.Test", "src\Plugins\Tailviewer.Formats.Serilog.Test\Tailviewer.Formats.Serilog.Test.csproj", "{B132CF5C-8555-4D0F-A55A-F870C003EB59}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tailviewer.DataSources.UDP", "src\Plugins\Tailviewer.DataSources.UDP\Tailviewer.DataSources.UDP.csproj", "{F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tailviewer.DataSources.UDP.Test", "src\Plugins\Tailviewer.DataSources.UDP.Test\Tailviewer.DataSources.UDP.Test.csproj", "{589025EC-2835-485C-BDCF-D9B77C65167E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -134,6 +139,14 @@ Global
{B132CF5C-8555-4D0F-A55A-F870C003EB59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B132CF5C-8555-4D0F-A55A-F870C003EB59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B132CF5C-8555-4D0F-A55A-F870C003EB59}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {589025EC-2835-485C-BDCF-D9B77C65167E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {589025EC-2835-485C-BDCF-D9B77C65167E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {589025EC-2835-485C-BDCF-D9B77C65167E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {589025EC-2835-485C-BDCF-D9B77C65167E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -157,6 +170,8 @@ Global
{A628892A-4BE2-4726-983E-CCE47C716CC1} = {3E228233-3DBD-48B5-85B3-BEAD0C65F084}
{9997F434-58B2-4C26-B622-5CA411A82BF7} = {BC0C0BBB-9D4C-47CE-9888-EB956F4A4562}
{B132CF5C-8555-4D0F-A55A-F870C003EB59} = {790E58D1-E0A7-4B82-B617-3A5D442B8CC0}
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22} = {BC0C0BBB-9D4C-47CE-9888-EB956F4A4562}
+ {589025EC-2835-485C-BDCF-D9B77C65167E} = {790E58D1-E0A7-4B82-B617-3A5D442B8CC0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B0BB6C40-85F7-419C-8CBC-89473DC30B16}
diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs
index 99faf1219..8262a4c56 100644
--- a/src/GlobalAssemblyInfo.cs
+++ b/src/GlobalAssemblyInfo.cs
@@ -11,6 +11,6 @@
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.9.3.0")]
-[assembly: AssemblyFileVersion("0.9.3.0")]
-[assembly: AssemblyInformationalVersion("0.9.3.0")]
+[assembly: AssemblyVersion("0.9.4.0")]
+[assembly: AssemblyFileVersion("0.9.4.0")]
+[assembly: AssemblyInformationalVersion("0.9.4.0")]
diff --git a/src/Installer/Installer.csproj b/src/Installer/Installer.csproj
index 3068f5a20..de9d67538 100644
--- a/src/Installer/Installer.csproj
+++ b/src/Installer/Installer.csproj
@@ -183,6 +183,9 @@
InstallationFiles\Plugins\Tailviewer.Formats.Serilog.0.0.0.tvp
+
+ InstallationFiles\Plugins\Tailviewer.DataSources.UDP.0.0.0.tvp
+
InstallationFiles\MMQ.dll
InstallationFiles\MMQ.dll
diff --git a/src/Plugins/Tailviewer.DataSources.UDP.Test/Class1.cs b/src/Plugins/Tailviewer.DataSources.UDP.Test/Class1.cs
new file mode 100644
index 000000000..4e766d7a8
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP.Test/Class1.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tailviewer.DataSources.UDP.Test
+{
+ public class Class1
+ {
+ }
+}
diff --git a/src/Plugins/Tailviewer.DataSources.UDP.Test/Properties/AssemblyInfo.cs b/src/Plugins/Tailviewer.DataSources.UDP.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..a50a5a194
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Tailviewer.DataSources.UDP.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Tailviewer.DataSources.UDP.Test")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("589025ec-2835-485c-bdcf-d9b77c65167e")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Plugins/Tailviewer.DataSources.UDP.Test/Tailviewer.DataSources.UDP.Test.csproj b/src/Plugins/Tailviewer.DataSources.UDP.Test/Tailviewer.DataSources.UDP.Test.csproj
new file mode 100644
index 000000000..527ed121b
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP.Test/Tailviewer.DataSources.UDP.Test.csproj
@@ -0,0 +1,42 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {589025EC-2835-485C-BDCF-D9B77C65167E}
+ Library
+ Properties
+ Tailviewer.DataSources.UDP.Test
+ Tailviewer.DataSources.UDP.Test
+ v4.7.1
+ 512
+ true
+
+
+ true
+ full
+ false
+ ..\..\..\bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\..\..\bin\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/IUdpSocket.cs b/src/Plugins/Tailviewer.DataSources.UDP/IUdpSocket.cs
new file mode 100644
index 000000000..00c77ca81
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/IUdpSocket.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Tailviewer.DataSources.UDP
+{
+ public interface IUdpSocket
+ : IDisposable
+ {
+ event Action OnMessage;
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/Properties/AssemblyInfo.cs b/src/Plugins/Tailviewer.DataSources.UDP/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..9cdabb043
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/Properties/AssemblyInfo.cs
@@ -0,0 +1,39 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Tailviewer.BusinessLogic.Plugins;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Tailviewer.DataSources.UDP")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Tailviewer.DataSources.UDP")]
+[assembly: AssemblyCopyright("Copyright © Simon Mießler 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f4f3d6cc-4dbb-40bc-bc12-7af88cfdab22")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+
+[assembly: PluginId("Tailviewer.DataSources", "UDP")]
+[assembly: PluginAuthor("Simon Mießler")]
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/Tailviewer.DataSources.UDP.csproj b/src/Plugins/Tailviewer.DataSources.UDP/Tailviewer.DataSources.UDP.csproj
new file mode 100644
index 000000000..684edd147
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/Tailviewer.DataSources.UDP.csproj
@@ -0,0 +1,77 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F4F3D6CC-4DBB-40BC-BC12-7AF88CFDAB22}
+ Library
+ Properties
+ Tailviewer.DataSources.UDP
+ Tailviewer.DataSources.UDP
+ v4.7.1
+ 512
+ true
+
+
+
+ true
+ full
+ false
+ ..\..\..\bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\..\..\bin\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\..\packages\log4net.2.0.12\lib\net45\log4net.dll
+
+
+
+
+
+
+ ..\..\..\packages\System.Threading.Extensions.2.0.59\lib\net45\System.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0C18B216-9FF4-4DCF-88C0-4B1448892F43}
+ Tailviewer.Api
+
+
+ {62C60D20-180E-4A59-9EF3-30161E1E31CB}
+ Tailviewer.Core
+
+
+
+
+
+
+
+
+ "archive.exe" pack "$(MSBuildProjectDirectory)\$(OutDir)Tailviewer.DataSources.UDP.dll"
+xcopy "$(MSBuildProjectDirectory)\$(OutDir)Tailviewer.DataSources.UDP.*.tvp" "$(MSBuildProjectDirectory)\$(OutDir)Plugins\" /y
+
+
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/UdpCustomDataSourceConfiguration.cs b/src/Plugins/Tailviewer.DataSources.UDP/UdpCustomDataSourceConfiguration.cs
new file mode 100644
index 000000000..ec0507b32
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/UdpCustomDataSourceConfiguration.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Xml;
+using Tailviewer.BusinessLogic.Plugins;
+
+namespace Tailviewer.DataSources.UDP
+{
+ public sealed class UdpCustomDataSourceConfiguration
+ : ICustomDataSourceConfiguration
+ {
+ public string Address;
+
+ #region Implementation of ICloneable
+
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ public UdpCustomDataSourceConfiguration Clone()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region Implementation of ICustomDataSourceConfiguration
+
+ public void Restore(XmlReader reader)
+ {
+ for(int i = 0; i < reader.AttributeCount; ++i)
+ {
+ reader.MoveToAttribute(i);
+ switch (reader.Name)
+ {
+ case "address":
+ Address = reader.Value;
+ break;
+ }
+ }
+ }
+
+ public void Store(XmlWriter writer)
+ {
+ writer.WriteAttributeString("address", Address);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/UdpDataSourcePlugin.cs b/src/Plugins/Tailviewer.DataSources.UDP/UdpDataSourcePlugin.cs
new file mode 100644
index 000000000..a5ad58cb5
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/UdpDataSourcePlugin.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.BusinessLogic.Plugins;
+using Tailviewer.Core.Settings;
+using Tailviewer.Ui;
+
+namespace Tailviewer.DataSources.UDP
+{
+ ///
+ ///
+ public sealed class UdpDataSourcePlugin
+ : ICustomDataSourcePlugin
+ {
+ #region Implementation of ICustomDataSourcePlugin
+
+ public string DisplayName
+ {
+ get { return "UDP"; }
+ }
+
+ public CustomDataSourceId Id
+ {
+ get { return new CustomDataSourceId("Tailviewer.DataSources.UDP.UdpDataSourcePlugin"); }
+ }
+
+ public ICustomDataSourceConfiguration CreateConfiguration(IServiceContainer serviceContainer)
+ {
+ return new UdpCustomDataSourceConfiguration();
+ }
+
+ public ICustomDataSourceViewModel CreateViewModel(IServiceContainer serviceContainer,
+ ICustomDataSourceConfiguration configuration)
+ {
+ return new UdpDataSourceViewModel((UdpCustomDataSourceConfiguration)configuration);
+ }
+
+ public FrameworkElement CreateConfigurationControl(IServiceContainer serviceContainer,
+ ICustomDataSourceViewModel
+ viewModel)
+ {
+ return new TextBlock {Text = "UDP Test"};
+ }
+
+ public ILogFile CreateLogFile(IServiceContainer serviceContainer, ICustomDataSourceConfiguration configuration)
+ {
+ var config = (UdpCustomDataSourceConfiguration) configuration;
+ var socket = new UdpSocket(ParseEndPoint(config.Address));
+ var defaultEncoding = serviceContainer.TryRetrieve()?.DefaultEncoding;
+ var overwrittenEncoding = serviceContainer.TryRetrieve();
+ var encoding = overwrittenEncoding ?? defaultEncoding ?? Encoding.UTF8;
+ return new UdpLogFile(serviceContainer.Retrieve(),
+ encoding,
+ socket);
+ }
+
+ private IPEndPoint ParseEndPoint(string configAddress)
+ {
+ var idx = configAddress.LastIndexOf(":");
+ if (idx == -1)
+ throw new ArgumentException($"Invalid IPEndPoint '{configAddress}': Expected a form of 'address:port'");
+
+ var address = IPAddress.Parse(configAddress.Substring(0, idx));
+ var port = int.Parse(configAddress.Substring(idx + 1));
+ return new IPEndPoint(address, port);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/UdpDataSourceViewModel.cs b/src/Plugins/Tailviewer.DataSources.UDP/UdpDataSourceViewModel.cs
new file mode 100644
index 000000000..d917aa771
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/UdpDataSourceViewModel.cs
@@ -0,0 +1,46 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using Tailviewer.Ui;
+
+namespace Tailviewer.DataSources.UDP
+{
+ public sealed class UdpDataSourceViewModel
+ : ICustomDataSourceViewModel
+ , INotifyPropertyChanged
+ {
+ private readonly UdpCustomDataSourceConfiguration _configuration;
+
+ public UdpDataSourceViewModel(UdpCustomDataSourceConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
+ public string Address
+ {
+ get { return _configuration.Address; }
+ set
+ {
+ if (value == _configuration.Address)
+ return;
+
+ _configuration.Address = value;
+ EmitPropertyChanged();
+ }
+ }
+
+ #region Implementation of ICustomDataSourceConfigurationViewModel
+
+
+ public event Action RequestStore;
+
+ #endregion
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void EmitPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/UdpDatagram.cs b/src/Plugins/Tailviewer.DataSources.UDP/UdpDatagram.cs
new file mode 100644
index 000000000..11cc6975f
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/UdpDatagram.cs
@@ -0,0 +1,10 @@
+using System.Net;
+
+namespace Tailviewer.DataSources.UDP
+{
+ public struct UdpDatagram
+ {
+ public IPEndPoint Sender;
+ public byte[] Message;
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/UdpLogFile.cs b/src/Plugins/Tailviewer.DataSources.UDP/UdpLogFile.cs
new file mode 100644
index 000000000..e8c050f3a
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/UdpLogFile.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using Tailviewer.BusinessLogic;
+using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.Core.LogFiles;
+
+namespace Tailviewer.DataSources.UDP
+{
+ public sealed class UdpLogFile
+ : AbstractLogFile
+ {
+ private readonly IUdpSocket _socket;
+ private readonly ConcurrentQueue _incomingDatagrams;
+ private readonly LogFilePropertyList _properties;
+
+ public UdpLogFile(ITaskScheduler scheduler,
+ Encoding encoding,
+ IUdpSocket socket)
+ : base(scheduler)
+ {
+ _properties = new LogFilePropertyList(LogFileProperties.Minimum);
+ _properties.SetValue(LogFileProperties.Encoding, encoding);
+
+ _socket = socket;
+ _incomingDatagrams = new ConcurrentQueue();
+
+ // DO NOT PUT ANY MORE INITIALIZATION BELOW HERE OR THINGS WILL GO HORRIBLY WRONG
+ _socket.OnMessage += OnMessageReceived;
+ }
+
+ private void OnMessageReceived(UdpDatagram datagram)
+ {
+ _incomingDatagrams.Enqueue(datagram);
+ }
+
+ #region Overrides of AbstractLogFile
+
+ protected override void DisposeAdditional()
+ {
+ _socket.OnMessage -= OnMessageReceived;
+ _socket?.Dispose();
+ }
+
+ public override int MaxCharactersPerLine
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override int Count
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override IReadOnlyList Columns
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override IReadOnlyList Properties
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override object GetValue(ILogFilePropertyDescriptor propertyDescriptor)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override T GetValue(ILogFilePropertyDescriptor propertyDescriptor)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void GetValues(ILogFileProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void GetColumn(LogFileSection section, ILogFileColumn column, T[] buffer, int destinationIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void GetColumn(IReadOnlyList indices, ILogFileColumn column, T[] buffer, int destinationIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void GetEntries(LogFileSection section, ILogEntries buffer, int destinationIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void GetEntries(IReadOnlyList indices, ILogEntries buffer, int destinationIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void GetSection(LogFileSection section, LogLine[] dest)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override LogLine GetLine(int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override double Progress
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ protected override TimeSpan RunOnce(CancellationToken token)
+ {
+ const int maxOps = 1000;
+ int numProcessed;
+ for (numProcessed = 0; numProcessed < maxOps; ++numProcessed)
+ {
+ if (!_incomingDatagrams.TryDequeue(out var datagram))
+ break;
+
+ Add(datagram);
+ }
+
+ if (numProcessed > 0)
+ return TimeSpan.Zero;
+
+ return TimeSpan.FromMilliseconds(500);
+ }
+
+ private void Add(UdpDatagram datagram)
+ {
+ var message = TryDecode(datagram.Message);
+ }
+
+ private string TryDecode(byte[] datagram)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/UdpSocket.cs b/src/Plugins/Tailviewer.DataSources.UDP/UdpSocket.cs
new file mode 100644
index 000000000..85fe48d99
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/UdpSocket.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using log4net;
+
+namespace Tailviewer.DataSources.UDP
+{
+ public sealed class UdpSocket
+ : IUdpSocket
+ {
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ private readonly IPEndPoint _localEndPoint;
+ private readonly byte[] _buffer;
+ private EndPoint _remoteEndPoint;
+ private readonly Socket _socket;
+ private bool _isDisposed;
+
+ public UdpSocket(IPEndPoint ipEndPoint)
+ {
+ _localEndPoint = ipEndPoint;
+ _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ _socket.SetSocketOption(SocketOptionLevel.Socket,
+ SocketOptionName.ReuseAddress, optionValue: true);
+ _socket.Bind(ipEndPoint);
+
+ const int bufferSize = 1024;
+ _buffer = new byte[bufferSize];
+
+ BeginReceive();
+ }
+
+ private void BeginReceive()
+ {
+ lock (_socket)
+ {
+ if (_isDisposed)
+ return;
+
+ EndPoint endPoint = _localEndPoint;
+ _socket.BeginReceiveFrom(_buffer, offset: 0, size: _buffer.Length,
+ socketFlags: SocketFlags.None,
+ remoteEP: ref endPoint,
+ callback: OnReceived,
+ state: null);
+ }
+ }
+
+ private void OnReceived(IAsyncResult ar)
+ {
+ try
+ {
+ EndPoint remoteEndPoint = _localEndPoint;
+ int bytesReceived;
+
+ lock (_socket)
+ {
+ if (_isDisposed)
+ return;
+
+ bytesReceived = _socket.EndReceiveFrom(ar, ref remoteEndPoint);
+ }
+
+ EmitOnMessage(bytesReceived, remoteEndPoint as IPEndPoint);
+ }
+ catch (SocketException e)
+ {
+ Log.ErrorFormat("Caught socket error: {0}", e);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ }
+ finally
+ {
+ BeginReceive();
+ }
+ }
+
+ private void EmitOnMessage(int bytesReceived, IPEndPoint sender)
+ {
+ try
+ {
+ var fn = OnMessage;
+ if (fn != null)
+ {
+ var buffer = new byte[bytesReceived];
+ _buffer.CopyTo(buffer, 0);
+ var datagram = new UdpDatagram
+ {
+ Message = buffer,
+ Sender = sender
+ };
+ fn(datagram);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ }
+ }
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ lock (_socket)
+ {
+ _socket.Dispose();
+ _isDisposed = true;
+ }
+ }
+
+ #endregion
+
+ #region Implementation of IUdpSocket
+
+ public event Action OnMessage;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/app.config b/src/Plugins/Tailviewer.DataSources.UDP/app.config
new file mode 100644
index 000000000..200c10db6
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.DataSources.UDP/packages.config b/src/Plugins/Tailviewer.DataSources.UDP/packages.config
new file mode 100644
index 000000000..83276ac62
--- /dev/null
+++ b/src/Plugins/Tailviewer.DataSources.UDP/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Plugins/Tailviewer.Formats.Serilog.Test/Tailviewer.Formats.Serilog.Test.csproj b/src/Plugins/Tailviewer.Formats.Serilog.Test/Tailviewer.Formats.Serilog.Test.csproj
index 29212d8ae..64420747a 100644
--- a/src/Plugins/Tailviewer.Formats.Serilog.Test/Tailviewer.Formats.Serilog.Test.csproj
+++ b/src/Plugins/Tailviewer.Formats.Serilog.Test/Tailviewer.Formats.Serilog.Test.csproj
@@ -20,7 +20,7 @@
true
full
false
- bin\Debug\
+ ..\..\..\bin\
DEBUG;TRACE
prompt
4
@@ -28,7 +28,7 @@
pdbonly
true
- bin\Release\
+ ..\..\..\bin\
TRACE
prompt
4
diff --git a/src/Plugins/Tailviewer.Formats.Serilog/Properties/AssemblyInfo.cs b/src/Plugins/Tailviewer.Formats.Serilog/Properties/AssemblyInfo.cs
index 907da69ac..8295c7c3c 100644
--- a/src/Plugins/Tailviewer.Formats.Serilog/Properties/AssemblyInfo.cs
+++ b/src/Plugins/Tailviewer.Formats.Serilog/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("Tailviewer.Serilog")]
+[assembly: AssemblyTitle("Tailviewer.Formats.Serilog")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Tailviewer.Serilog")]
+[assembly: AssemblyProduct("Tailviewer.Formats.Serilog")]
[assembly: AssemblyCopyright("Copyright © Simon Mießler 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceAcceptanceTest.cs b/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceAcceptanceTest.cs
similarity index 94%
rename from src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceAcceptanceTest.cs
rename to src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceAcceptanceTest.cs
index dcfe31c60..4d8d29982 100644
--- a/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceAcceptanceTest.cs
+++ b/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceAcceptanceTest.cs
@@ -11,7 +11,7 @@
namespace Tailviewer.AcceptanceTests.BusinessLogic.DataSources
{
[TestFixture]
- public sealed class SingleDataSourceAcceptanceTest
+ public sealed class FileDataSourceAcceptanceTest
{
private ManualTaskScheduler _scheduler;
private PluginLogFileFactory _logFileFactory;
@@ -32,7 +32,7 @@ public void TestLevelPrecedence()
{
Id = DataSourceId.CreateNew()
};
- using (var dataSource = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var dataSource = new FileDataSource(_logFileFactory, _scheduler, settings))
{
dataSource.FilteredLogFile.Property(x =>
{
diff --git a/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceManualTest.cs b/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceManualTest.cs
similarity index 89%
rename from src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceManualTest.cs
rename to src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceManualTest.cs
index 6ef90109f..2a6d9d977 100644
--- a/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceManualTest.cs
+++ b/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceManualTest.cs
@@ -15,7 +15,7 @@
namespace Tailviewer.AcceptanceTests.BusinessLogic.DataSources
{
[TestFixture]
- public sealed class SingleDataSourceManualTest
+ public sealed class FileDataSourceManualTest
{
private ManualTaskScheduler _scheduler;
private string _fname;
@@ -56,7 +56,7 @@ private TextLogFile Create(string fileName)
public void TestWrite1([Values(true, false)] bool isSingleLine)
{
_settings.IsSingleLine = isSingleLine;
- using (var dataSource = new SingleDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
{
_writer.Write("ssss");
_writer.Flush();
@@ -72,7 +72,7 @@ public void TestWrite1([Values(true, false)] bool isSingleLine)
public void TestWrite2([Values(true, false)] bool isSingleLine)
{
_settings.IsSingleLine = isSingleLine;
- using (var dataSource = new SingleDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
{
_writer.Write("Hello World\r\n");
_writer.Flush();
@@ -88,7 +88,7 @@ public void TestWrite2([Values(true, false)] bool isSingleLine)
public void TestWrite3([Values(true, false)] bool isSingleLine)
{
_settings.IsSingleLine = isSingleLine;
- using (var dataSource = new SingleDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
{
_writer.Write("Hello World\r\n");
_writer.Flush();
@@ -109,7 +109,7 @@ public void TestWrite3([Values(true, false)] bool isSingleLine)
public void TestReadOneLine3([Values(true, false)] bool isSingleLine)
{
_settings.IsSingleLine = isSingleLine;
- using (var dataSource = new SingleDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
{
_writer.Write("A");
_writer.Flush();
@@ -133,7 +133,7 @@ public void TestReadOneLine3([Values(true, false)] bool isSingleLine)
public void TestReadMultiline()
{
_settings.IsSingleLine = false;
- using (var dataSource = new SingleDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
{
_writer.WriteLine("2015-10-07 19:50:58,981 INFO Starting");
_writer.WriteLine("the application...");
@@ -153,7 +153,7 @@ public void TestReadMultiline()
public void TestReadSingleLine()
{
_settings.IsSingleLine = true;
- using (var dataSource = new SingleDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, _settings, _logFile, TimeSpan.Zero))
{
_writer.WriteLine("2015-10-07 19:50:58,981 INFO Starting");
_writer.WriteLine("the application...");
diff --git a/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceRealTest.cs b/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceRealTest.cs
similarity index 93%
rename from src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceRealTest.cs
rename to src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceRealTest.cs
index 50dcc1970..02598cd01 100644
--- a/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/SingleDataSourceRealTest.cs
+++ b/src/Tailviewer.AcceptanceTests/BusinessLogic/DataSources/FileDataSourceRealTest.cs
@@ -13,7 +13,7 @@
namespace Tailviewer.AcceptanceTests.BusinessLogic.DataSources
{
[TestFixture]
- public sealed class SingleDataSourceRealTest
+ public sealed class FileDataSourceRealTest
{
[SetUp]
public void SetUp()
@@ -24,7 +24,7 @@ public void SetUp()
{
Id = DataSourceId.CreateNew()
};
- _dataSource = new SingleDataSource(_logFileFactory, _scheduler, _settings, TimeSpan.FromMilliseconds(100));
+ _dataSource = new FileDataSource(_logFileFactory, _scheduler, _settings, TimeSpan.FromMilliseconds(100));
}
[TearDown]
@@ -35,7 +35,7 @@ public void TearDown()
}
private DataSource _settings;
- private SingleDataSource _dataSource;
+ private FileDataSource _dataSource;
private DefaultTaskScheduler _scheduler;
private ILogFileFactory _logFileFactory;
diff --git a/src/Tailviewer.AcceptanceTests/Tailviewer.AcceptanceTests.csproj b/src/Tailviewer.AcceptanceTests/Tailviewer.AcceptanceTests.csproj
index afea501af..921dc5516 100644
--- a/src/Tailviewer.AcceptanceTests/Tailviewer.AcceptanceTests.csproj
+++ b/src/Tailviewer.AcceptanceTests/Tailviewer.AcceptanceTests.csproj
@@ -94,9 +94,9 @@
-
-
-
+
+
+
@@ -117,7 +117,7 @@
-
+
diff --git a/src/Tailviewer.AcceptanceTests/Ui/ViewModels/SingleDataSourceViewModelTest.cs b/src/Tailviewer.AcceptanceTests/Ui/ViewModels/FileDataSourceViewModelTest.cs
similarity index 88%
rename from src/Tailviewer.AcceptanceTests/Ui/ViewModels/SingleDataSourceViewModelTest.cs
rename to src/Tailviewer.AcceptanceTests/Ui/ViewModels/FileDataSourceViewModelTest.cs
index 45580f5c6..40398eff4 100644
--- a/src/Tailviewer.AcceptanceTests/Ui/ViewModels/SingleDataSourceViewModelTest.cs
+++ b/src/Tailviewer.AcceptanceTests/Ui/ViewModels/FileDataSourceViewModelTest.cs
@@ -16,7 +16,7 @@
namespace Tailviewer.AcceptanceTests.Ui.ViewModels
{
[TestFixture]
- public sealed class SingleDataSourceViewModelTest
+ public sealed class FileDataSourceViewModelTest
{
private DefaultTaskScheduler _taskScheduler;
@@ -41,9 +41,9 @@ public void TestSearch1()
{
var settings = new DataSource(TextLogFileAcceptanceTest.File2Mb) { Id = DataSourceId.CreateNew() };
using (var logFile = Create(TextLogFileAcceptanceTest.File2Mb))
- using (var dataSource = new SingleDataSource(_taskScheduler, settings, logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_taskScheduler, settings, logFile, TimeSpan.Zero))
{
- var model = new SingleDataSourceViewModel(dataSource, new Mock().Object);
+ var model = new FileDataSourceViewModel(dataSource, new Mock().Object);
logFile.Property(x => x.EndOfSourceReached).ShouldEventually().BeTrue();
@@ -72,9 +72,9 @@ public void TestSearch1()
public void TestCannotBeRemovedAsPartOfFolder()
{
var actionCenter = new Mock();
- var dataSource = new Mock();
+ var dataSource = new Mock();
dataSource.Setup(x => x.Settings).Returns(new DataSource());
- var model = new SingleDataSourceViewModel(dataSource.Object, actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource.Object, actionCenter.Object);
using (var monitor = model.Monitor())
{
model.CanBeRemoved.Should().BeTrue();
@@ -99,9 +99,9 @@ public void TestCannotBeRemovedAsPartOfFolder()
public void TestCanBeRemovedAsPartOfMergedDataSource()
{
var actionCenter = new Mock();
- var dataSource = new Mock();
+ var dataSource = new Mock();
dataSource.Setup(x => x.Settings).Returns(new DataSource());
- var model = new SingleDataSourceViewModel(dataSource.Object, actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource.Object, actionCenter.Object);
using (var monitor = model.Monitor())
{
model.CanBeRemoved.Should().BeTrue();
@@ -124,8 +124,8 @@ public void TestCanBeRemovedAsPartOfMergedDataSource()
[Issue("https://github.com/Kittyfisto/Tailviewer/issues/215")]
public void TestClearAllShowAll()
{
- var dataSource = new Mock();
- var model = new SingleDataSourceViewModel(dataSource.Object, new Mock().Object);
+ var dataSource = new Mock();
+ var model = new FileDataSourceViewModel(dataSource.Object, new Mock().Object);
model.ScreenCleared.Should().BeFalse();
diff --git a/src/Tailviewer.AcceptanceTests/Ui/ViewModels/LogViewerViewModelTest.cs b/src/Tailviewer.AcceptanceTests/Ui/ViewModels/LogViewerViewModelTest.cs
index aeb26442e..6b5e2b344 100644
--- a/src/Tailviewer.AcceptanceTests/Ui/ViewModels/LogViewerViewModelTest.cs
+++ b/src/Tailviewer.AcceptanceTests/Ui/ViewModels/LogViewerViewModelTest.cs
@@ -47,10 +47,10 @@ public void SetUp()
public void TestSearch1()
{
using (
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource(TextLogFileAcceptanceTest.File20Mb) {Id = DataSourceId.CreateNew()}))
{
- var dataSourceModel = new SingleDataSourceViewModel(dataSource, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings, TimeSpan.Zero);
dataSourceModel.SearchTerm = "i";
diff --git a/src/Tailviewer.Api/BusinessLogic/Plugins/CustomDataSourceId.cs b/src/Tailviewer.Api/BusinessLogic/Plugins/CustomDataSourceId.cs
new file mode 100644
index 000000000..c10b12373
--- /dev/null
+++ b/src/Tailviewer.Api/BusinessLogic/Plugins/CustomDataSourceId.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace Tailviewer.BusinessLogic.Plugins
+{
+ ///
+ /// Uniquely identifies a implementation.
+ ///
+ ///
+ /// The string itself should be unique enough so the chances that another plugin picks the same value is very small.
+ /// Further more, once a plugin is released to the public, then this value shouldn't be changed anymore because it
+ /// is used to properly restore a previously added custom data source when tailviewer is restarted.
+ ///
+ public sealed class CustomDataSourceId
+ {
+ ///
+ ///
+ public readonly string Id;
+
+ ///
+ ///
+ ///
+ public CustomDataSourceId(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ throw new ArgumentException("The id should neither be null, nor empty", "id");
+
+ Id = id;
+ }
+
+ #region Equality members
+
+ private bool Equals(CustomDataSourceId other)
+ {
+ return string.Equals(Id, other.Id);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override bool Equals(object obj)
+ {
+ return ReferenceEquals(this, obj) || obj is CustomDataSourceId other && Equals(other);
+ }
+
+ ///
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ return Id != null ? Id.GetHashCode() : 0;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool operator ==(CustomDataSourceId left, CustomDataSourceId right)
+ {
+ return Equals(left, right);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool operator !=(CustomDataSourceId left, CustomDataSourceId right)
+ {
+ return !Equals(left, right);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer.Api/BusinessLogic/Plugins/ICustomDataSourceConfiguration.cs b/src/Tailviewer.Api/BusinessLogic/Plugins/ICustomDataSourceConfiguration.cs
new file mode 100644
index 000000000..0d1859dcc
--- /dev/null
+++ b/src/Tailviewer.Api/BusinessLogic/Plugins/ICustomDataSourceConfiguration.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Xml;
+
+namespace Tailviewer.BusinessLogic.Plugins
+{
+ ///
+ /// The interface to hold the configuration necessary to access a custom data source.
+ ///
+ ///
+ /// When tailviewer stores this configuration, it first clones it on the UI thread
+ /// (via ) and then eventually calls
+ /// on the cloned object on a background thread prior to writing the configuration to disk.
+ ///
+ /// Restoring is generally only done once while the application is started and happens on an unspecified thread.
+ ///
+ public interface ICustomDataSourceConfiguration
+ : ICloneable
+ {
+ ///
+ /// Is called usually after construction in case a previous data source is restored, for example
+ /// if tailviewer was just started and the custom data source was previously stored.
+ /// Otherwise, this method is not called.
+ ///
+ ///
+ /// An implementation should try to restore its configuration from the given blob or throw an exception
+ /// in case that is not possible.
+ ///
+ ///
+ void Restore(XmlReader reader);
+
+ ///
+ /// Is called by tailviewer whenever it deems it necessary to store the custom data source's configuration.
+ ///
+ ///
+ /// An implementation should try to store the important information that shall be persisted in between tailviewer
+ /// sessions in the given writer. Information which is not written to the writer (and then not later restored via
+ /// will be lost!).
+ ///
+ ///
+ /// s
+ ///
+ void Store(XmlWriter writer);
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer.Api/BusinessLogic/Plugins/ICustomDataSourcePlugin.cs b/src/Tailviewer.Api/BusinessLogic/Plugins/ICustomDataSourcePlugin.cs
new file mode 100644
index 000000000..e3c6d9014
--- /dev/null
+++ b/src/Tailviewer.Api/BusinessLogic/Plugins/ICustomDataSourcePlugin.cs
@@ -0,0 +1,89 @@
+using System.Windows;
+using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.Ui;
+
+namespace Tailviewer.BusinessLogic.Plugins
+{
+ ///
+ /// The plugin interface for a non-file based data source which nontheless contains log events.
+ ///
+ ///
+ /// File-less data sources are usually only accessible via some sort of API, be it an operating system
+ /// API or something more specific. Either way, a plugin must be implemented which takes care of accessing
+ /// this API and converting its log events into a format which tailviewer can understand, namely .
+ ///
+ ///
+ /// An example implementation can be found in the 'Tailviewer.DataSources.UDP' plugin which is part of the
+ /// Tailviewer repository. This plugin takes care of interpreting a stream of UDP datagrams send to a particular
+ /// end-point as log events where each datagram contains a singular log event in plain text.
+ ///
+ public interface ICustomDataSourcePlugin
+ : IPlugin
+ {
+ ///
+ /// The name of this custom data source.
+ ///
+ ///
+ /// This name is presented to users when they select which custom data source to add to tailviewer and should
+ /// be understandable enough for them to make a meaningful decision.
+ ///
+ string DisplayName { get; }
+
+ ///
+ /// Uniquely identifies this implementation.
+ ///
+ CustomDataSourceId Id { get; }
+
+ ///
+ ///
+ ///
+ /// Tailviewer will call this method whenever the a new data source is created or an existing data source
+ /// is restored, for example after tailviewer is started. Tailviewer may invoke
+ ///
+ /// right after construction in case the data source already existed (or its configuration was stored on disk).
+ /// Either way, the object created through this method will then be forwarded to
+ /// and at the appropriate time.
+ ///
+ /// The service container containing a myriad of services which this plugin may access
+ ///
+ ICustomDataSourceConfiguration CreateConfiguration(IServiceContainer serviceContainer);
+
+ ///
+ /// Creates a new view model which allows the user to view and edit the configuration of the data source.
+ ///
+ ///
+ ///
+ /// The service container containing a myriad of services which this plugin may access
+ ///
+ ///
+ ICustomDataSourceViewModel CreateViewModel(IServiceContainer serviceContainer,
+ ICustomDataSourceConfiguration configuration);
+
+ ///
+ /// Creates a new control which realizes the input elements to allow a user to view and edit the configuration.
+ ///
+ ///
+ /// Tailviewer will call this method with a view model previously created with
+ /// . This implementation needs to take care of binding the
+ /// properties of the view model to the created control!
+ ///
+ /// The service container containing a myriad of services which this plugin may access
+ ///
+ ///
+ FrameworkElement CreateConfigurationControl(IServiceContainer serviceContainer,
+ ICustomDataSourceViewModel viewModel);
+
+ ///
+ /// Creates a new instance which provides the data source's log events to tailviewer.
+ ///
+ ///
+ /// The implementation will have to make sure to convert the data source's log events to tailviewer's interface
+ /// and to notify tailviewer whenever the data source changes.
+ ///
+ /// The service container containing a myriad of services which this plugin may access
+ ///
+ ///
+ ILogFile CreateLogFile(IServiceContainer serviceContainer,
+ ICustomDataSourceConfiguration configuration);
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer.Api/Tailviewer.Api.csproj b/src/Tailviewer.Api/Tailviewer.Api.csproj
index 040fddae5..787d573a0 100644
--- a/src/Tailviewer.Api/Tailviewer.Api.csproj
+++ b/src/Tailviewer.Api/Tailviewer.Api.csproj
@@ -53,10 +53,13 @@
..\..\packages\System.Threading.Extensions.2.0.59\lib\net45\System.Extensions.dll
-
+
+
+
+
@@ -69,6 +72,7 @@
+
diff --git a/src/Tailviewer.Api/Ui/ICustomDataSourceViewModel.cs b/src/Tailviewer.Api/Ui/ICustomDataSourceViewModel.cs
new file mode 100644
index 000000000..406cd72ca
--- /dev/null
+++ b/src/Tailviewer.Api/Ui/ICustomDataSourceViewModel.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Tailviewer.Ui
+{
+ ///
+ /// This interface should be implemented by plugins to allow a user to view / edit the configuration
+ /// of a custom data source.
+ ///
+ ///
+ /// An example of this implementation can be found in the `Tailviewer.DataSources.UDP` plugin which is part
+ /// of this repository.
+ ///
+ public interface ICustomDataSourceViewModel
+ {
+ ///
+ /// Whenever this event is fired, tailviewer will store the information contained in this configuration.
+ ///
+ ///
+ /// By default, tailviewer will store the configurations when the application exits normally. This event may
+ /// be fired in case this isn't good enough and the information should be stored earlier.
+ ///
+ event Action RequestStore;
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer.Archiver/Plugins/PluginAssemblyLoader.cs b/src/Tailviewer.Archiver/Plugins/PluginAssemblyLoader.cs
index ebd042a5a..480a92565 100644
--- a/src/Tailviewer.Archiver/Plugins/PluginAssemblyLoader.cs
+++ b/src/Tailviewer.Archiver/Plugins/PluginAssemblyLoader.cs
@@ -39,7 +39,8 @@ static PluginAssemblyLoader()
typeof(ILogFileIssuesPlugin),
typeof(ILogFileFormatMatcherPlugin),
typeof(ICustomLogFileFormatCreatorPlugin),
- typeof(ITextLogFileParserPlugin)
+ typeof(ITextLogFileParserPlugin),
+ typeof(ICustomDataSourcePlugin)
};
}
diff --git a/src/Tailviewer.Test/BusinessLogic/DataSources/DataSourcesTest.cs b/src/Tailviewer.Test/BusinessLogic/DataSources/DataSourcesTest.cs
index 84304ce24..61f4f0f73 100644
--- a/src/Tailviewer.Test/BusinessLogic/DataSources/DataSourcesTest.cs
+++ b/src/Tailviewer.Test/BusinessLogic/DataSources/DataSourcesTest.cs
@@ -48,7 +48,7 @@ public void TearDown()
[Test]
public void TestAddFile()
{
- SingleDataSource source = _dataSources.AddFile(@"E:\Code\test.log");
+ FileDataSource source = _dataSources.AddFile(@"E:\Code\test.log");
source.Should().NotBeNull();
source.FullFileName.Should().Be(@"E:\Code\test.log");
source.FollowTail.Should().BeFalse();
@@ -263,8 +263,8 @@ public void TestCtor5()
[Test]
public void TestRemove()
{
- SingleDataSource source1 = _dataSources.AddFile(@"E:\Code\test1.log");
- SingleDataSource source2 = _dataSources.AddFile(@"E:\Code\test2.log");
+ FileDataSource source1 = _dataSources.AddFile(@"E:\Code\test1.log");
+ FileDataSource source2 = _dataSources.AddFile(@"E:\Code\test2.log");
_dataSources.Remove(source1);
_settings.Count.Should().Be(1);
diff --git a/src/Tailviewer.Test/BusinessLogic/DataSources/SingleDataSourceTest.cs b/src/Tailviewer.Test/BusinessLogic/DataSources/FileDataSourceTest.cs
similarity index 85%
rename from src/Tailviewer.Test/BusinessLogic/DataSources/SingleDataSourceTest.cs
rename to src/Tailviewer.Test/BusinessLogic/DataSources/FileDataSourceTest.cs
index 1e9d0178a..e7ba8b0cf 100644
--- a/src/Tailviewer.Test/BusinessLogic/DataSources/SingleDataSourceTest.cs
+++ b/src/Tailviewer.Test/BusinessLogic/DataSources/FileDataSourceTest.cs
@@ -15,7 +15,7 @@
namespace Tailviewer.Test.BusinessLogic.DataSources
{
[TestFixture]
- public sealed class SingleDataSourceTest
+ public sealed class FileDataSourceTest
{
[SetUp]
public void SetUp()
@@ -48,7 +48,7 @@ public void SetUp()
[Test]
public void TestConstruction1()
{
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, new DataSource(@"E:\somelogfile.txt") { Id = DataSourceId.CreateNew() }))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, new DataSource(@"E:\somelogfile.txt") { Id = DataSourceId.CreateNew() }))
{
source.FullFileName.Should().Be(@"E:\somelogfile.txt");
source.LevelFilter.Should().Be(LevelFlags.All);
@@ -65,7 +65,7 @@ public void TestConstruction2()
Id = DataSourceId.CreateNew(),
SelectedLogLines = new HashSet {1, 2}
};
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, settings))
{
source.SelectedLogLines.Should().BeEquivalentTo(new LogLineIndex[] {1, 2});
}
@@ -79,7 +79,7 @@ public void TestConstruction3([Values(true, false)] bool showDeltaTimes)
Id = DataSourceId.CreateNew(),
ShowDeltaTimes = showDeltaTimes
};
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, settings))
{
source.ShowDeltaTimes.Should().Be(showDeltaTimes);
}
@@ -93,7 +93,7 @@ public void TestConstruction4([Values(true, false)] bool showElapsedTime)
Id = DataSourceId.CreateNew(),
ShowElapsedTime = showElapsedTime
};
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, settings))
{
source.ShowElapsedTime.Should().Be(showElapsedTime);
}
@@ -106,7 +106,7 @@ public void TestChangeShowElapsedTime([Values(true, false)] bool showElapsedTime
{
Id = DataSourceId.CreateNew()
};
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, settings))
{
source.ShowElapsedTime = showElapsedTime;
settings.ShowElapsedTime.Should().Be(showElapsedTime);
@@ -123,7 +123,7 @@ public void TestChangeShowDeltaTimes([Values(true, false)] bool showDeltaTimes)
{
Id = DataSourceId.CreateNew()
};
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, settings))
{
source.ShowDeltaTimes = showDeltaTimes;
settings.ShowDeltaTimes.Should().Be(showDeltaTimes);
@@ -143,8 +143,8 @@ public void TestDispose1()
LogFileProxy permanentFindAllLogFile;
LogFileSearchProxy permanentFindAllSearch;
- SingleDataSource source;
- using (source = new SingleDataSource(_logFileFactory, _scheduler, new DataSource(@"E:\somelogfile.txt") {Id = DataSourceId.CreateNew()}))
+ FileDataSource source;
+ using (source = new FileDataSource(_logFileFactory, _scheduler, new DataSource(@"E:\somelogfile.txt") {Id = DataSourceId.CreateNew()}))
{
permanentLogFile = (LogFileProxy) source.FilteredLogFile;
permanentLogFile.IsDisposed.Should().BeFalse();
@@ -169,7 +169,7 @@ public void TestDispose1()
[Description("Verifies that the data source stops all periodic tasks upon being disposed of")]
public void TestDispose2()
{
- SingleDataSource source = new SingleDataSource(_logFileFactory, _scheduler,
+ FileDataSource source = new FileDataSource(_logFileFactory, _scheduler,
new DataSource(@"E:\somelogfile.txt") {Id = DataSourceId.CreateNew()});
_scheduler.PeriodicTaskCount.Should().BeGreaterThan(0);
source.Dispose();
@@ -180,7 +180,7 @@ public void TestDispose2()
[Description("Verifies that the levels are counted correctly")]
public void TestLevelCount1()
{
- using (var dataSource = new SingleDataSource(_logFileFactory, _scheduler, new DataSource(@"TestData\LevelCounts.txt") { Id = DataSourceId.CreateNew() }))
+ using (var dataSource = new FileDataSource(_logFileFactory, _scheduler, new DataSource(@"TestData\LevelCounts.txt") { Id = DataSourceId.CreateNew() }))
{
_scheduler.Run(2);
dataSource.UnfilteredLogFile.Property(x => x.EndOfSourceReached).ShouldEventually().BeTrue();
@@ -199,7 +199,7 @@ public void TestLevelCount1()
[Test]
public void TestSearch1()
{
- using (var dataSource = new SingleDataSource(_scheduler, CreateDataSource(), _logFile.Object, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, CreateDataSource(), _logFile.Object, TimeSpan.Zero))
{
_entries.Add(new LogLine(0, 0, "Hello foobar world!", LevelFlags.Other));
_listeners.OnRead(1);
@@ -216,7 +216,7 @@ public void TestSearch1()
public void TestHideEmptyLines1()
{
var settings = CreateDataSource();
- using (var dataSource = new SingleDataSource(_scheduler, settings, _logFile.Object, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, settings, _logFile.Object, TimeSpan.Zero))
{
dataSource.HideEmptyLines.Should().BeFalse();
dataSource.HideEmptyLines = true;
@@ -231,7 +231,7 @@ public void TestHideEmptyLines1()
public void TestIsSingleLine()
{
var settings = CreateDataSource();
- using (var dataSource = new SingleDataSource(_scheduler, settings, _logFile.Object, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, settings, _logFile.Object, TimeSpan.Zero))
{
dataSource.IsSingleLine.Should().BeFalse();
dataSource.IsSingleLine = true;
@@ -251,7 +251,7 @@ public void TestClearScreen()
var logFile = new InMemoryLogFile();
logFile.AddEntry("Foo");
logFile.AddEntry("Bar");
- using (var dataSource = new SingleDataSource(_scheduler, settings, logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, settings, logFile, TimeSpan.Zero))
{
_scheduler.RunOnce();
dataSource.FilteredLogFile.Count.Should().Be(2);
@@ -277,7 +277,7 @@ public void TestClearScreenShowAll()
var logFile = new InMemoryLogFile();
logFile.AddEntry("Foo");
logFile.AddEntry("Bar");
- using (var dataSource = new SingleDataSource(_scheduler, settings, logFile, TimeSpan.Zero))
+ using (var dataSource = new FileDataSource(_scheduler, settings, logFile, TimeSpan.Zero))
{
_scheduler.RunOnce();
diff --git a/src/Tailviewer.Test/BusinessLogic/DataSources/MergedDataSourceTest.cs b/src/Tailviewer.Test/BusinessLogic/DataSources/MergedDataSourceTest.cs
index 03e8c104c..752e96dd9 100644
--- a/src/Tailviewer.Test/BusinessLogic/DataSources/MergedDataSourceTest.cs
+++ b/src/Tailviewer.Test/BusinessLogic/DataSources/MergedDataSourceTest.cs
@@ -36,7 +36,7 @@ public void SetUp()
public void TestAdd1()
{
var settings = new DataSource("foo") {Id = DataSourceId.CreateNew()};
- var dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings);
+ var dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings);
_merged.Add(dataSource);
settings.ParentId.Should()
.Be(_settings.Id, "Because the parent-child relationship should've been declared via ParentId");
@@ -47,7 +47,7 @@ public void TestAdd1()
public void TestAdd2()
{
var settings = new DataSource("foo") {Id = DataSourceId.CreateNew()};
- var dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings);
+ var dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings);
_merged.Add(dataSource);
_merged.UnfilteredLogFile.Should().NotBeNull();
_merged.UnfilteredLogFile.Should().BeOfType();
@@ -59,7 +59,7 @@ public void TestAdd2()
public void TestAdd3()
{
var settings = new DataSource("foo") {Id = DataSourceId.CreateNew()};
- var dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings);
+ var dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings);
ILogFile logFile1 = _merged.UnfilteredLogFile;
_merged.Add(dataSource);
@@ -73,7 +73,7 @@ public void TestAdd3()
public void TestMultiline()
{
var settings = new DataSource("foo") { Id = DataSourceId.CreateNew() };
- var dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings);
+ var dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings);
ILogFile logFile1 = _merged.UnfilteredLogFile;
_merged.Add(dataSource);
@@ -90,7 +90,7 @@ public void TestChangeFilter1()
ILogFile logFile1 = _merged.UnfilteredLogFile;
_merged.SearchTerm = "foo";
var settings1 = new DataSource("foo") {Id = DataSourceId.CreateNew()};
- var dataSource1 = new SingleDataSource(_logFileFactory, _taskScheduler, settings1);
+ var dataSource1 = new FileDataSource(_logFileFactory, _taskScheduler, settings1);
_merged.Add(dataSource1);
ILogFile logFile2 = _merged.UnfilteredLogFile;
@@ -147,7 +147,7 @@ public void TestDispose1()
public void TestDispose2()
{
var settings = new DataSource("foo") { Id = DataSourceId.CreateNew() };
- var dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings);
+ var dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings);
_merged.Add(dataSource);
_merged.Remove(dataSource);
dataSource.Dispose();
@@ -162,7 +162,7 @@ public void TestDispose2()
public void TestRemove1()
{
var settings = new DataSource("foo") {Id = DataSourceId.CreateNew()};
- var dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings);
+ var dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings);
_merged.Add(dataSource);
_merged.Remove(dataSource);
dataSource.Settings.ParentId.Should().Be(DataSourceId.Empty);
@@ -173,11 +173,11 @@ public void TestRemove1()
public void TestRemove2()
{
var settings1 = new DataSource("foo") {Id = DataSourceId.CreateNew()};
- var dataSource1 = new SingleDataSource(_logFileFactory, _taskScheduler, settings1);
+ var dataSource1 = new FileDataSource(_logFileFactory, _taskScheduler, settings1);
_merged.Add(dataSource1);
var settings2 = new DataSource("bar") {Id = DataSourceId.CreateNew()};
- var dataSource2 = new SingleDataSource(_logFileFactory, _taskScheduler, settings2);
+ var dataSource2 = new FileDataSource(_logFileFactory, _taskScheduler, settings2);
_merged.Add(dataSource2);
_merged.Remove(dataSource2);
@@ -202,14 +202,14 @@ public void TestMergeMultiline1()
{
var logFile1 = new InMemoryLogFile();
var source1Id = new LogLineSourceId(0);
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
var logFile2 = new InMemoryLogFile();
var source2Id = new LogLineSourceId(1);
- var source2 = new SingleDataSource(_taskScheduler,
+ var source2 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile2,
TimeSpan.Zero);
@@ -248,7 +248,7 @@ public void TestSetDataSourcesEmpty()
public void TestSetDataSourcesOneSource()
{
var logFile1 = new InMemoryLogFile();
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
@@ -262,7 +262,7 @@ public void TestSetDataSourcesOneSource()
public void TestSetDataSourcesOneNewSource()
{
var logFile1 = new InMemoryLogFile();
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
@@ -270,7 +270,7 @@ public void TestSetDataSourcesOneNewSource()
_merged.SetDataSources(new []{source1});
var logFile2 = new InMemoryLogFile();
- var source2 = new SingleDataSource(_taskScheduler,
+ var source2 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile2,
TimeSpan.Zero);
@@ -286,13 +286,13 @@ public void TestSetDataSourcesOneNewSource()
public void TestSetDataSourcesOneLessSource()
{
var logFile1 = new InMemoryLogFile();
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
var logFile2 = new InMemoryLogFile();
- var source2 = new SingleDataSource(_taskScheduler,
+ var source2 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile2,
TimeSpan.Zero);
@@ -312,19 +312,19 @@ public void TestSetDataSourcesOneLessSource()
public void TestDataSourceOrder()
{
var logFile2 = new InMemoryLogFile();
- var source2 = new SingleDataSource(_taskScheduler,
+ var source2 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile2,
TimeSpan.Zero);
var logFile1 = new InMemoryLogFile();
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
var logFile3 = new InMemoryLogFile();
- var source3 = new SingleDataSource(_taskScheduler,
+ var source3 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile3,
TimeSpan.Zero);
@@ -338,13 +338,13 @@ public void TestDataSourceOrder()
public void TestExcludeDataSource1()
{
var logFile1 = new InMemoryLogFile();
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
var logFile2 = new InMemoryLogFile();
- var source2 = new SingleDataSource(_taskScheduler,
+ var source2 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile2,
TimeSpan.Zero);
@@ -365,7 +365,7 @@ public void TestExcludeDataSource1()
public void TestExcludeDataSource2()
{
var logFile1 = new InMemoryLogFile();
- var source1 = new SingleDataSource(_taskScheduler,
+ var source1 = new FileDataSource(_taskScheduler,
new DataSource { Id = DataSourceId.CreateNew() },
logFile1,
TimeSpan.Zero);
diff --git a/src/Tailviewer.Test/SimplePluginLogFileFactory.cs b/src/Tailviewer.Test/SimplePluginLogFileFactory.cs
index c76b6f248..e0bdf8fd3 100644
--- a/src/Tailviewer.Test/SimplePluginLogFileFactory.cs
+++ b/src/Tailviewer.Test/SimplePluginLogFileFactory.cs
@@ -12,7 +12,7 @@ public sealed class SimplePluginLogFileFactory
: PluginLogFileFactory
{
public SimplePluginLogFileFactory(ITaskScheduler scheduler, params IFileFormatPlugin[] plugins)
- : base(CreateServiceContainer(scheduler), plugins.Select(x => new PluginWithDescription(x, null)))
+ : base(CreateServiceContainer(scheduler), plugins.Select(x => new PluginWithDescription(x, null)), null)
{}
private static IServiceContainer CreateServiceContainer(ITaskScheduler scheduler)
diff --git a/src/Tailviewer.Test/Tailviewer.Test.csproj b/src/Tailviewer.Test/Tailviewer.Test.csproj
index 9991dacff..47a08dd26 100644
--- a/src/Tailviewer.Test/Tailviewer.Test.csproj
+++ b/src/Tailviewer.Test/Tailviewer.Test.csproj
@@ -172,7 +172,7 @@
-
+
@@ -243,7 +243,7 @@
-
+
diff --git a/src/Tailviewer.Test/Ui/Controls/LogViewerControlTest.cs b/src/Tailviewer.Test/Ui/Controls/LogViewerControlTest.cs
index bac341b2f..0cdda79e5 100644
--- a/src/Tailviewer.Test/Ui/Controls/LogViewerControlTest.cs
+++ b/src/Tailviewer.Test/Ui/Controls/LogViewerControlTest.cs
@@ -20,7 +20,7 @@ public sealed class LogViewerControlTest
{
private Mock _actionCenter;
private LogViewerControl _control;
- private SingleDataSourceViewModel _dataSource;
+ private FileDataSourceViewModel _dataSource;
private ILogFileFactory _logFileFactory;
private ManualTaskScheduler _scheduler;
private Mock _settings;
@@ -38,8 +38,8 @@ public void SetUp()
{
_settings = new Mock();
_dataSource =
- new SingleDataSourceViewModel(
- new SingleDataSource(_logFileFactory, _scheduler, new DataSource("Foobar") {Id = DataSourceId.CreateNew()}),
+ new FileDataSourceViewModel(
+ new FileDataSource(_logFileFactory, _scheduler, new DataSource("Foobar") {Id = DataSourceId.CreateNew()}),
_actionCenter.Object);
_control = new LogViewerControl
{
@@ -366,8 +366,8 @@ public void TestChangeShowWarning()
public void TestCtor()
{
var source =
- new SingleDataSourceViewModel(
- new SingleDataSource(_logFileFactory, _scheduler, new DataSource("Foobar") {Id = DataSourceId.CreateNew()}),
+ new FileDataSourceViewModel(
+ new FileDataSource(_logFileFactory, _scheduler, new DataSource("Foobar") {Id = DataSourceId.CreateNew()}),
_actionCenter.Object);
source.LevelsFilter = LevelFlags.All;
diff --git a/src/Tailviewer.Test/Ui/Controls/MainPanel/LogViewMainPanelViewModelTest.cs b/src/Tailviewer.Test/Ui/Controls/MainPanel/LogViewMainPanelViewModelTest.cs
index 2a90e20f0..142c8e9a2 100644
--- a/src/Tailviewer.Test/Ui/Controls/MainPanel/LogViewMainPanelViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/Controls/MainPanel/LogViewMainPanelViewModelTest.cs
@@ -15,6 +15,7 @@
using Tailviewer.BusinessLogic.Filters;
using Tailviewer.BusinessLogic.Highlighters;
using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.BusinessLogic.Plugins;
using Tailviewer.Core;
using Tailviewer.Core.LogFiles;
using Tailviewer.Settings;
@@ -47,6 +48,7 @@ public void Setup()
_actionCenter = new Mock();
_dataSources = new Mock();
_dataSources.Setup(x => x.Sources).Returns(new List());
+ _dataSources.Setup(x => x.CustomDataSources).Returns(new List());
_quickFilters = new Mock();
_quickFilters.Setup(x => x.AddQuickFilter()).Returns(new QuickFilter(new Core.Settings.QuickFilter()));
diff --git a/src/Tailviewer.Test/Ui/DataSourcesControlTest.cs b/src/Tailviewer.Test/Ui/DataSourcesControlTest.cs
index 32366c17a..e36b13e3c 100644
--- a/src/Tailviewer.Test/Ui/DataSourcesControlTest.cs
+++ b/src/Tailviewer.Test/Ui/DataSourcesControlTest.cs
@@ -48,7 +48,7 @@ public void TestFilter2()
{
var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
@@ -58,21 +58,21 @@ public void TestFilter2()
[Test]
public void TestFilter3()
{
- var sources = new ObservableCollection();
+ var sources = new ObservableCollection();
_control.ItemsSource = sources;
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources);
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources);
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources);
}
@@ -80,17 +80,17 @@ public void TestFilter3()
[Test]
public void TestFilter4()
{
- var sources = new ObservableCollection();
+ var sources = new ObservableCollection();
_control.ItemsSource = sources;
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
sources.RemoveAt(1);
@@ -106,13 +106,13 @@ public void TestFilter4()
[Test]
public void TestFilter5()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.StringFilter = "2";
@@ -123,9 +123,9 @@ public void TestFilter5()
[Test]
public void TestFilter6()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.StringFilter = "2";
@@ -133,10 +133,10 @@ public void TestFilter6()
_control.FilteredItemsSource.Should().BeEmpty();
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
sources.Add(
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources[1]);
}
@@ -144,13 +144,13 @@ public void TestFilter6()
[Test]
public void TestFilter7()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
@@ -168,13 +168,13 @@ public void TestFilter7()
[Test]
public void TestFilter8()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
@@ -193,14 +193,14 @@ public void TestFilter8()
[Description("Verifies that inserting an item at the first position WITHOUT a filter works")]
public void TestInsertAt1()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
sources.Insert(0,
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources);
}
@@ -209,14 +209,14 @@ public void TestInsertAt1()
[Description("Verifies that inserting an item at the last position WITHOUT a filter works")]
public void TestInsertAt2()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
sources.Insert(1,
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources);
}
@@ -225,16 +225,16 @@ public void TestInsertAt2()
[Description("Verifies that inserting an item in the middle WITHOUT a filter works")]
public void TestInsertAt3()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test1.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
sources.Insert(1,
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(sources);
}
@@ -243,18 +243,18 @@ public void TestInsertAt3()
[Description("Verifies that inserting an item in the middle WITH a filter works")]
public void TestInsertAt4()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("foo.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
// Let's set a filter that causes the first element to be hidden
_control.StringFilter = "test";
sources.Insert(1,
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(new object[] {sources[1], sources[2]});
}
@@ -263,20 +263,20 @@ public void TestInsertAt4()
[Description("Verifies that inserting an item in the middle WITH a filter works")]
public void TestInsertAt5()
{
- var sources = new ObservableCollection
+ var sources = new ObservableCollection
{
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test1.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("foo.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object),
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test2.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
_control.ItemsSource = sources;
// Let's set a filter that causes the first element to be hidden
_control.StringFilter = "test";
sources.Insert(2,
- new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("test3.log") {Id = DataSourceId.CreateNew()}), _actionCenter.Object));
_control.FilteredItemsSource.Should().Equal(new object[] {sources[0], sources[2], sources[3]});
diff --git a/src/Tailviewer.Test/Ui/DataSourcesViewModelTest.cs b/src/Tailviewer.Test/Ui/DataSourcesViewModelTest.cs
index 8781ec629..4dd913562 100644
--- a/src/Tailviewer.Test/Ui/DataSourcesViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/DataSourcesViewModelTest.cs
@@ -206,7 +206,7 @@ public void TestCtor1()
viewModel = _model.Observable[1];
viewModel.Should().NotBeNull();
- viewModel.Should().BeOfType();
+ viewModel.Should().BeOfType();
viewModel.DataSource.Id.Should().Be(source3.Id);
}
@@ -231,7 +231,7 @@ public void TestCtor2()
viewModel = _model.Observable[1];
viewModel.Should().NotBeNull();
- viewModel.Should().BeOfType();
+ viewModel.Should().BeOfType();
}
[Test]
diff --git a/src/Tailviewer.Test/Ui/SingleDataSourceViewModelTest.cs b/src/Tailviewer.Test/Ui/FileDataSourceViewModelTest.cs
similarity index 82%
rename from src/Tailviewer.Test/Ui/SingleDataSourceViewModelTest.cs
rename to src/Tailviewer.Test/Ui/FileDataSourceViewModelTest.cs
index cd9789cc0..3416a9287 100644
--- a/src/Tailviewer.Test/Ui/SingleDataSourceViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/FileDataSourceViewModelTest.cs
@@ -16,7 +16,7 @@
namespace Tailviewer.Test.Ui
{
[TestFixture]
- public sealed class SingleDataSourceViewModelTest
+ public sealed class FileDataSourceViewModelTest
{
private ILogFileFactory _logFileFactory;
private ManualTaskScheduler _scheduler;
@@ -37,9 +37,9 @@ public void TestConstruction1()
{
Id = DataSourceId.CreateNew()
};
- using (var source = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var source = new FileDataSource(_logFileFactory, _scheduler, settings))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
model.FullName.Should().Be(@"E:\Code\SharpTail\SharpTail.Test\TestData\20Mb.test");
model.Id.Should().Be(settings.Id);
@@ -52,13 +52,13 @@ public void TestConstruction1()
public void TestConstruction2()
{
using (
- var source = new SingleDataSource(_scheduler,
+ var source = new FileDataSource(_scheduler,
new DataSource {Id = DataSourceId.CreateNew(), File = @"C:\temp\foo.txt", SearchTerm = "foobar"},
new Mock().Object, TimeSpan.Zero))
{
source.SearchTerm.Should().Be("foobar");
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
model.SearchTerm.Should().Be("foobar");
}
}
@@ -66,14 +66,14 @@ public void TestConstruction2()
[Test]
public void TestConstruction3([Values(true, false)] bool showDeltaTimes)
{
- using (var source = new SingleDataSource(_scheduler, new DataSource
+ using (var source = new FileDataSource(_scheduler, new DataSource
{
Id = DataSourceId.CreateNew(),
File = @"C:\temp\foo.txt",
ShowDeltaTimes = showDeltaTimes
}, new Mock().Object, TimeSpan.Zero))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
model.ShowDeltaTimes.Should().Be(showDeltaTimes);
}
}
@@ -81,14 +81,14 @@ public void TestConstruction3([Values(true, false)] bool showDeltaTimes)
[Test]
public void TestConstruction4([Values(true, false)] bool showElapsedTime)
{
- using (var source = new SingleDataSource(_scheduler, new DataSource
+ using (var source = new FileDataSource(_scheduler, new DataSource
{
Id = DataSourceId.CreateNew(),
File = @"C:\temp\foo.txt",
ShowElapsedTime = showElapsedTime
}, new Mock().Object, TimeSpan.Zero))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
model.ShowElapsedTime.Should().Be(showElapsedTime);
}
}
@@ -96,14 +96,14 @@ public void TestConstruction4([Values(true, false)] bool showElapsedTime)
[Test]
public void TestChangeShowElapsedTime([Values(true, false)] bool showElapsedTime)
{
- using (var source = new SingleDataSource(_scheduler, new DataSource
+ using (var source = new FileDataSource(_scheduler, new DataSource
{
Id = DataSourceId.CreateNew(),
File = @"C:\temp\foo.txt",
ShowElapsedTime = showElapsedTime
}, new Mock().Object, TimeSpan.Zero))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
var changes = new List();
model.PropertyChanged += (sender, args) => changes.Add(args.PropertyName);
@@ -122,14 +122,14 @@ public void TestChangeShowElapsedTime([Values(true, false)] bool showElapsedTime
[Test]
public void TestChangeShowDeltaTimes([Values(true, false)] bool showDeltaTimes)
{
- using (var source = new SingleDataSource(_scheduler, new DataSource
+ using (var source = new FileDataSource(_scheduler, new DataSource
{
Id = DataSourceId.CreateNew(),
File = @"C:\temp\foo.txt",
ShowDeltaTimes = showDeltaTimes
}, new Mock().Object, TimeSpan.Zero))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
var changes = new List();
model.PropertyChanged += (sender, args) => changes.Add(args.PropertyName);
@@ -148,9 +148,9 @@ public void TestChangeShowDeltaTimes([Values(true, false)] bool showDeltaTimes)
[Test]
public void TestRename()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
dataSource.Setup(x => x.FullFileName).Returns("A:\\foo");
- var model = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
model.DisplayName.Should().Be("foo");
new Action(() => model.DisplayName = "bar").Should().Throw();
@@ -162,10 +162,10 @@ public void TestRemoveCommand1()
{
using (
var source =
- new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSource(_logFileFactory, _scheduler,
new DataSource(@"E:\Code\SharpTail\SharpTail.Test\TestData\20Mb.test") {Id = DataSourceId.CreateNew()}))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
model.RemoveCommand.Should().NotBeNull();
model.RemoveCommand.CanExecute(null).Should().BeTrue();
new Action(() => model.RemoveCommand.Execute(null)).Should().NotThrow();
@@ -177,10 +177,10 @@ public void TestRemoveCommand2()
{
using (
var source =
- new SingleDataSource(_logFileFactory, _scheduler,
+ new FileDataSource(_logFileFactory, _scheduler,
new DataSource(@"E:\Code\SharpTail\SharpTail.Test\TestData\20Mb.test") {Id = DataSourceId.CreateNew()}))
{
- var model = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(source, _actionCenter.Object);
var calls = new List();
model.Remove += calls.Add;
new Action(() => model.RemoveCommand.Execute(null)).Should().NotThrow();
@@ -196,9 +196,9 @@ public void TestSetQuickFilterChain1()
{
Id = DataSourceId.CreateNew()
};
- using (var dataSource = new SingleDataSource(_logFileFactory, _scheduler, settings))
+ using (var dataSource = new FileDataSource(_logFileFactory, _scheduler, settings))
{
- var model = new SingleDataSourceViewModel(dataSource, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource, _actionCenter.Object);
var chain = new[] {new SubstringFilter("foobar", true)};
model.QuickFilterChain = chain;
model.QuickFilterChain.Should().BeSameAs(chain);
@@ -209,10 +209,10 @@ public void TestSetQuickFilterChain1()
[Test]
public void TestCharacterCode()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
dataSource.SetupAllProperties();
- var model = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
model.CharacterCode = "ZZ";
model.CharacterCode.Should().Be("ZZ");
dataSource.Object.CharacterCode.Should().Be("ZZ");
@@ -230,10 +230,10 @@ public void TestCharacterCode()
[Issue("https://github.com/Kittyfisto/Tailviewer/issues/125")]
public void TestDisplayRelativePathWithFolderParent()
{
- var single = new Mock();
+ var single = new Mock();
single.Setup(x => x.Settings).Returns(new DataSource());
single.Setup(x => x.FullFileName).Returns(@"C:\Users\Simon\AppData\Local\Tailviewer\Installation.log");
- var singleViewModel = new SingleDataSourceViewModel(single.Object, _actionCenter.Object);
+ var singleViewModel = new FileDataSourceViewModel(single.Object, _actionCenter.Object);
using (var monitor = singleViewModel.Monitor())
{
singleViewModel.DisplayName.Should().Be("Installation.log");
@@ -260,11 +260,11 @@ public void TestDisplayRelativePathWithFolderParent()
[Test]
public void TestPluginDescription()
{
- var single = new Mock();
+ var single = new Mock();
single.Setup(x => x.Settings).Returns(new DataSource());
var pluginDescription = new PluginDescription();
single.Setup(x => x.TranslationPlugin).Returns(pluginDescription);
- var singleViewModel = new SingleDataSourceViewModel(single.Object, _actionCenter.Object);
+ var singleViewModel = new FileDataSourceViewModel(single.Object, _actionCenter.Object);
singleViewModel.TranslationPlugin.Should().NotBeNull();
singleViewModel.TranslationPlugin.Should().BeSameAs(pluginDescription);
}
@@ -273,10 +273,10 @@ public void TestPluginDescription()
[Description("Verifies that the description of the context menu item changes even when the ExcludeFromParent property is updated on its own")]
public void TestToggleExcludeViaModel()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
dataSource.Setup(x => x.Settings).Returns(new DataSource());
dataSource.Setup(x => x.FullFileName).Returns("A:\\foo");
- var model = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var parent = new Mock();
var parentDataSource = new Mock();
parentDataSource.Setup(x => x.Id).Returns(DataSourceId.CreateNew());
@@ -298,10 +298,10 @@ public void TestToggleExcludeViaModel()
[Test]
public void TestToggleExcludeViaContextMenu()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
dataSource.Setup(x => x.Settings).Returns(new DataSource());
dataSource.Setup(x => x.FullFileName).Returns("A:\\foo");
- var model = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var model = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var parent = new Mock();
var parentDataSource = new Mock();
parentDataSource.Setup(x => x.Id).Returns(DataSourceId.CreateNew());
diff --git a/src/Tailviewer.Test/Ui/LogViewerViewModelTest.cs b/src/Tailviewer.Test/Ui/LogViewerViewModelTest.cs
index dacb629fc..6d7d6d77c 100644
--- a/src/Tailviewer.Test/Ui/LogViewerViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/LogViewerViewModelTest.cs
@@ -32,7 +32,7 @@ public void SetUp()
[Test]
public void TestDataSourceDoesntExist1()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.SourceDoesNotExist);
var filteredLogFile = new Mock();
@@ -41,7 +41,7 @@ public void TestDataSourceDoesntExist1()
dataSource.Setup(x => x.FilteredLogFile).Returns(filteredLogFile.Object);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
model.NoEntriesExplanation.Should().Be("Can't find \"somefile.log\"");
@@ -52,7 +52,7 @@ public void TestDataSourceDoesntExist1()
[Description("Verifies that the NoEntriesSubtext is cleared when the reason why no entries are showing up changes")]
public void TestDataSourceDoesntExist2()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.SourceDoesNotExist);
logFile.Setup(x => x.GetValue(LogFileProperties.Size)).Returns((Size?)null);
@@ -65,7 +65,7 @@ public void TestDataSourceDoesntExist2()
dataSource.Setup(x => x.FilteredLogFile).Returns(filteredLogFile.Object);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
model.NoEntriesExplanation.Should().Be("Can't find \"somefile.log\"");
@@ -83,7 +83,7 @@ public void TestDataSourceDoesntExist2()
[Test]
public void TestDataSourceCannotBeAccessed1()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.SourceCannotBeAccessed);
var filteredLogFile = new Mock();
@@ -92,7 +92,7 @@ public void TestDataSourceCannotBeAccessed1()
dataSource.Setup(x => x.FilteredLogFile).Returns(filteredLogFile.Object);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
model.NoEntriesExplanation.Should().Be("Unable to access \"somefile.log\"");
@@ -102,7 +102,7 @@ public void TestDataSourceCannotBeAccessed1()
[Test]
public void TestDataSourceEmpty()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.None);
logFile.Setup(x => x.GetValue(LogFileProperties.Size)).Returns(Size.Zero);
@@ -111,7 +111,7 @@ public void TestDataSourceEmpty()
dataSource.Setup(x => x.FilteredLogFile).Returns(filteredLogFile.Object);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
model.NoEntriesExplanation.Should().Be("The data source is empty");
@@ -143,7 +143,7 @@ public void TestDataSourceEmpty()
[Test]
public void TestLevelFilter1([ValueSource(nameof(NotAll))] LevelFlags flags)
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.None);
logFile.Setup(x => x.Count).Returns(1);
@@ -154,7 +154,7 @@ public void TestLevelFilter1([ValueSource(nameof(NotAll))] LevelFlags flags)
dataSource.Setup(x => x.LevelFilter).Returns(flags);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
@@ -165,7 +165,7 @@ public void TestLevelFilter1([ValueSource(nameof(NotAll))] LevelFlags flags)
[Test]
public void TestQuickFilter()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.None);
logFile.Setup(x => x.Count).Returns(1);
@@ -178,7 +178,7 @@ public void TestQuickFilter()
dataSource.Setup(x => x.LevelFilter).Returns(LevelFlags.All);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
model.NoEntriesExplanation.Should().Be("Not a single log entry matches the activated quick filters");
@@ -188,7 +188,7 @@ public void TestQuickFilter()
[Test]
public void TestStringFilter()
{
- var dataSource = new Mock();
+ var dataSource = new Mock();
var logFile = new Mock();
logFile.Setup(x => x.GetValue(LogFileProperties.EmptyReason)).Returns(ErrorFlags.None);
logFile.Setup(x => x.Count).Returns(1);
@@ -200,7 +200,7 @@ public void TestStringFilter()
dataSource.Setup(x => x.LevelFilter).Returns(LevelFlags.All);
dataSource.Setup(x => x.Search).Returns(new Mock().Object);
- var dataSourceModel = new SingleDataSourceViewModel(dataSource.Object, _actionCenter.Object);
+ var dataSourceModel = new FileDataSourceViewModel(dataSource.Object, _actionCenter.Object);
var model = new LogViewerViewModel(dataSourceModel, _actionCenter.Object, _settings.Object, TimeSpan.Zero);
model.LogEntryCount.Should().Be(0);
model.NoEntriesExplanation.Should().Be("Not a single log entry matches the log file filter");
diff --git a/src/Tailviewer.Test/Ui/MergedDataSourceViewModelTest.cs b/src/Tailviewer.Test/Ui/MergedDataSourceViewModelTest.cs
index 4d4db656f..d383fe6f1 100644
--- a/src/Tailviewer.Test/Ui/MergedDataSourceViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/MergedDataSourceViewModelTest.cs
@@ -115,8 +115,8 @@ public void TestExpand()
public void TestAddChild1()
{
var model = new MergedDataSourceViewModel(_dataSources.AddGroup(), _actionCenter.Object);
- SingleDataSource source = _dataSources.AddFile("foo");
- var sourceViewModel = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ FileDataSource source = _dataSources.AddFile("foo");
+ var sourceViewModel = new FileDataSourceViewModel(source, _actionCenter.Object);
model.AddChild(sourceViewModel);
model.Observable.Should().Equal(sourceViewModel);
sourceViewModel.Parent.Should().BeSameAs(model);
@@ -128,8 +128,8 @@ public void TestAddChild2()
var dataSource = _dataSources.AddGroup();
var model = new MergedDataSourceViewModel(dataSource, _actionCenter.Object);
- SingleDataSource source = _dataSources.AddFile("foo");
- var sourceViewModel = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ FileDataSource source = _dataSources.AddFile("foo");
+ var sourceViewModel = new FileDataSourceViewModel(source, _actionCenter.Object);
model.AddChild(sourceViewModel);
sourceViewModel.CharacterCode.Should().Be("A", "because the merged data source is responsible for providing unique character codes");
@@ -142,18 +142,18 @@ public void TestAddChild3()
var dataSource = _dataSources.AddGroup();
var model = new MergedDataSourceViewModel(dataSource, _actionCenter.Object);
- var sources = new List();
+ var sources = new List();
for (int i = 0; i < LogLineSourceId.MaxSources; ++i)
{
var source = _dataSources.AddFile(i.ToString());
- var sourceViewModel = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ var sourceViewModel = new FileDataSourceViewModel(source, _actionCenter.Object);
sources.Add(sourceViewModel);
model.AddChild(sourceViewModel).Should().BeTrue("because the child should've been added");
model.Observable.Should().Equal(sources, "because all previously added children should be there");
}
- var tooMuch = new SingleDataSourceViewModel(_dataSources.AddFile("dadw"), _actionCenter.Object);
+ var tooMuch = new FileDataSourceViewModel(_dataSources.AddFile("dadw"), _actionCenter.Object);
model.AddChild(tooMuch).Should().BeFalse("because no more children can be added");
model.Observable.Should().Equal(sources, "because only those sources which could be added should be present");
}
@@ -164,8 +164,8 @@ public void TestInsertChild1()
var dataSource = _dataSources.AddGroup();
var model = new MergedDataSourceViewModel(dataSource, _actionCenter.Object);
- SingleDataSource source = _dataSources.AddFile("foo");
- var sourceViewModel = new SingleDataSourceViewModel(source, _actionCenter.Object);
+ FileDataSource source = _dataSources.AddFile("foo");
+ var sourceViewModel = new FileDataSourceViewModel(source, _actionCenter.Object);
model.Insert(0, sourceViewModel);
sourceViewModel.CharacterCode.Should().Be("A", "because the merged data source is responsible for providing unique character codes");
@@ -177,11 +177,11 @@ public void TestInsertChild2()
var dataSource = _dataSources.AddGroup();
var model = new MergedDataSourceViewModel(dataSource, _actionCenter.Object);
- var child1 = new SingleDataSourceViewModel(_dataSources.AddFile("foo"), _actionCenter.Object);
+ var child1 = new FileDataSourceViewModel(_dataSources.AddFile("foo"), _actionCenter.Object);
model.AddChild(child1);
child1.CharacterCode.Should().Be("A");
- var child2 = new SingleDataSourceViewModel(_dataSources.AddFile("bar"), _actionCenter.Object);
+ var child2 = new FileDataSourceViewModel(_dataSources.AddFile("bar"), _actionCenter.Object);
model.Insert(0, child2);
model.Observable.Should().Equal(new object[]
{
@@ -199,10 +199,10 @@ public void TestRemoveChild1()
var dataSource = _dataSources.AddGroup();
var model = new MergedDataSourceViewModel(dataSource, _actionCenter.Object);
- var child1 = new SingleDataSourceViewModel(_dataSources.AddFile("foo"), _actionCenter.Object);
+ var child1 = new FileDataSourceViewModel(_dataSources.AddFile("foo"), _actionCenter.Object);
model.AddChild(child1);
- var child2 = new SingleDataSourceViewModel(_dataSources.AddFile("bar"), _actionCenter.Object);
+ var child2 = new FileDataSourceViewModel(_dataSources.AddFile("bar"), _actionCenter.Object);
model.AddChild(child2);
model.Observable.Should().Equal(new object[]
{
diff --git a/src/Tailviewer.Test/Ui/QuickFilterViewModelTest.cs b/src/Tailviewer.Test/Ui/QuickFilterViewModelTest.cs
index ecc324f76..913e8de61 100644
--- a/src/Tailviewer.Test/Ui/QuickFilterViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/QuickFilterViewModelTest.cs
@@ -26,7 +26,7 @@ public void OneTimeSetUp()
public void SetUp()
{
_quickFilter = new QuickFilter(new Core.Settings.QuickFilter());
- _dataSource = new SingleDataSource(_logFileFactory, _scheduler, _dataSourceSettings = new DataSource("nothing") {Id = DataSourceId.CreateNew()});
+ _dataSource = new FileDataSource(_logFileFactory, _scheduler, _dataSourceSettings = new DataSource("nothing") {Id = DataSourceId.CreateNew()});
_model = new QuickFilterViewModel(_quickFilter, x => { })
{
CurrentDataSource = _dataSource
@@ -36,7 +36,7 @@ public void SetUp()
}
private QuickFilter _quickFilter;
- private SingleDataSource _dataSource;
+ private FileDataSource _dataSource;
private QuickFilterViewModel _model;
private DataSource _dataSourceSettings;
private List _changes;
diff --git a/src/Tailviewer.Test/Ui/QuickFiltersSidePanelViewModelTest.cs b/src/Tailviewer.Test/Ui/QuickFiltersSidePanelViewModelTest.cs
index aa38e8016..662de4bd4 100644
--- a/src/Tailviewer.Test/Ui/QuickFiltersSidePanelViewModelTest.cs
+++ b/src/Tailviewer.Test/Ui/QuickFiltersSidePanelViewModelTest.cs
@@ -107,9 +107,9 @@ public void TestDontAddNeedlessEmptyFilters()
public void TestAdd()
{
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters);
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource("sw") {Id = DataSourceId.CreateNew()});
- model.CurrentDataSource = new SingleDataSourceViewModel(dataSource, _actionCenter.Object);
+ model.CurrentDataSource = new FileDataSourceViewModel(dataSource, _actionCenter.Object);
var filter = model.AddQuickFilter();
filter.CurrentDataSource.Should().BeSameAs(dataSource);
}
@@ -118,12 +118,12 @@ public void TestAdd()
public void TestChangeCurrentDataSource()
{
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters);
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource("sw") {Id = DataSourceId.CreateNew()});
var filter = model.AddQuickFilter();
filter.CurrentDataSource.Should().BeNull();
- model.CurrentDataSource = new SingleDataSourceViewModel(dataSource, _actionCenter.Object);
+ model.CurrentDataSource = new FileDataSourceViewModel(dataSource, _actionCenter.Object);
filter.CurrentDataSource.Should().BeSameAs(dataSource);
model.CurrentDataSource = null;
@@ -135,7 +135,7 @@ public void TestChangeFilterType1()
{
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters)
{
- CurrentDataSource = new SingleDataSourceViewModel(new SingleDataSource(_logFileFactory, _scheduler,
+ CurrentDataSource = new FileDataSourceViewModel(new FileDataSource(_logFileFactory, _scheduler,
new DataSource("adw") {Id = DataSourceId.CreateNew()}), _actionCenter.Object)
};
@@ -190,14 +190,14 @@ public void TestCtor1()
public void TestCtor2()
{
var filter1 = _quickFilters.AddQuickFilter();
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource("daw") {Id = DataSourceId.CreateNew()});
dataSource.ActivateQuickFilter(filter1.Id);
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters);
var changed = 0;
model.OnFiltersChanged += () => ++changed;
- model.CurrentDataSource = new SingleDataSourceViewModel(dataSource, _actionCenter.Object);
+ model.CurrentDataSource = new FileDataSourceViewModel(dataSource, _actionCenter.Object);
changed.Should().Be(1, "Because changing the current data source MUST apply ");
}
@@ -206,13 +206,13 @@ public void TestCtor2()
public void TestRemove1()
{
var filter1 = _quickFilters.AddQuickFilter();
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource("daw") {Id = DataSourceId.CreateNew()});
dataSource.ActivateQuickFilter(filter1.Id);
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters)
{
- CurrentDataSource = new SingleDataSourceViewModel(dataSource, _actionCenter.Object)
+ CurrentDataSource = new FileDataSourceViewModel(dataSource, _actionCenter.Object)
};
var filter1Model = model.QuickFilters.First();
@@ -229,13 +229,13 @@ public void TestRemove1()
public void TestRemove2()
{
var filter1 = _quickFilters.AddQuickFilter();
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource("daw") {Id = DataSourceId.CreateNew()});
dataSource.ActivateQuickFilter(filter1.Id);
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters)
{
- CurrentDataSource = new SingleDataSourceViewModel(dataSource, _actionCenter.Object)
+ CurrentDataSource = new FileDataSourceViewModel(dataSource, _actionCenter.Object)
};
var filter1Model = model.QuickFilters.First();
filter1Model.IsActive = false;
@@ -255,9 +255,9 @@ public void TestActivate1()
var model = new QuickFiltersSidePanelViewModel(_settings, _quickFilters);
model.QuickInfo.Should().BeNull();
- var dataSource = new SingleDataSource(_logFileFactory, _scheduler,
+ var dataSource = new FileDataSource(_logFileFactory, _scheduler,
new DataSource("daw") {Id = DataSourceId.CreateNew()});
- model.CurrentDataSource = new SingleDataSourceViewModel(dataSource, _actionCenter.Object);
+ model.CurrentDataSource = new FileDataSourceViewModel(dataSource, _actionCenter.Object);
model.QuickFilters.ElementAt(0).IsActive = true;
model.QuickInfo.Should().Be("1 active");
diff --git a/src/Tailviewer/App.cs b/src/Tailviewer/App.cs
index 72b2ddf41..4c32fd5f5 100644
--- a/src/Tailviewer/App.cs
+++ b/src/Tailviewer/App.cs
@@ -18,6 +18,7 @@
using log4net.Layout;
using log4net.Repository.Hierarchy;
using Tailviewer.Archiver.Plugins;
+using Tailviewer.BusinessLogic.DataSources.Custom;
using Tailviewer.BusinessLogic.Highlighters;
using Tailviewer.BusinessLogic.LogFileFormats;
using Tailviewer.BusinessLogic.LogFiles;
@@ -180,7 +181,8 @@ private static int StartApplication(SingleApplicationHelper.IMutex mutex, string
services.RegisterInstance(textLogFileParserPlugin);
var fileFormatPlugins = pluginSystem.LoadAllOfTypeWithDescription();
- var logFileFactory = new PluginLogFileFactory(services, fileFormatPlugins);
+ var customDataSourcePlugins = pluginSystem.LoadAllOfTypeWithDescription();
+ var logFileFactory = new PluginLogFileFactory(services, fileFormatPlugins, customDataSourcePlugins);
using (var dataSources = new DataSources(logFileFactory, taskScheduler, filesystem, settings.DataSources, bookmarks))
using (var updater = new AutoUpdater(actionCenter, settings.AutoUpdate))
{
diff --git a/src/Tailviewer/BusinessLogic/DataSources/Custom/CustomDataSource.cs b/src/Tailviewer/BusinessLogic/DataSources/Custom/CustomDataSource.cs
new file mode 100644
index 000000000..af0883acb
--- /dev/null
+++ b/src/Tailviewer/BusinessLogic/DataSources/Custom/CustomDataSource.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading;
+using Tailviewer.Archiver.Plugins.Description;
+using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.BusinessLogic.Plugins;
+using Tailviewer.Core.LogFiles;
+using Tailviewer.Settings;
+
+namespace Tailviewer.BusinessLogic.DataSources.Custom
+{
+ public sealed class CustomDataSource
+ : AbstractDataSource
+ , ICustomDataSource
+ {
+ private readonly ILogFile _originalLogFile;
+ private readonly LogFileProxy _unfilteredLogFile;
+ private readonly IPluginDescription _pluginDescription;
+ private MultiLineLogFile _multiLineLogFile;
+
+ public CustomDataSource(ILogFileFactory logFileFactory,
+ ITaskScheduler taskScheduler,
+ DataSource settings,
+ TimeSpan maximumWaitTime)
+ : base(taskScheduler, settings, maximumWaitTime)
+ {
+ _originalLogFile =
+ logFileFactory.CreateCustom(settings.CustomDataSourceId, settings.CustomDataSourceConfiguration,
+ out _pluginDescription);
+ _unfilteredLogFile = new LogFileProxy(TaskScheduler, MaximumWaitTime);
+ OnSingleLineChanged();
+ OnUnfilteredLogFileChanged();
+ }
+
+ public override IPluginDescription TranslationPlugin => _pluginDescription;
+
+ public override ILogFile OriginalLogFile => _originalLogFile;
+
+ public override ILogFile UnfilteredLogFile => _unfilteredLogFile;
+
+ protected override void OnSingleLineChanged()
+ {
+ _multiLineLogFile?.Dispose();
+
+ if (!IsSingleLine)
+ {
+ _multiLineLogFile = new MultiLineLogFile(TaskScheduler, _originalLogFile, MaximumWaitTime);
+ _unfilteredLogFile.InnerLogFile = _multiLineLogFile;
+ }
+ else
+ {
+ _unfilteredLogFile.InnerLogFile = _originalLogFile;
+ }
+ }
+
+ protected override void DisposeAdditional()
+ {
+ _originalLogFile?.Dispose();
+ _unfilteredLogFile?.Dispose();
+ _multiLineLogFile?.Dispose();
+ }
+
+ #region Implementation of ICustomDataSource
+
+ public ICustomDataSourceConfiguration Configuration => Settings.CustomDataSourceConfiguration;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/BusinessLogic/DataSources/Custom/ICustomDataSource.cs b/src/Tailviewer/BusinessLogic/DataSources/Custom/ICustomDataSource.cs
new file mode 100644
index 000000000..03d859705
--- /dev/null
+++ b/src/Tailviewer/BusinessLogic/DataSources/Custom/ICustomDataSource.cs
@@ -0,0 +1,13 @@
+using Tailviewer.BusinessLogic.Plugins;
+
+namespace Tailviewer.BusinessLogic.DataSources.Custom
+{
+ ///
+ /// The interface for a custom data source, e.g. one that is integrated into tailviewer via a plugin.
+ ///
+ public interface ICustomDataSource
+ : ISingleDataSource
+ {
+ ICustomDataSourceConfiguration Configuration { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/BusinessLogic/DataSources/Custom/NoThrowCustomDataSourceConfiguration.cs b/src/Tailviewer/BusinessLogic/DataSources/Custom/NoThrowCustomDataSourceConfiguration.cs
new file mode 100644
index 000000000..d24a5b461
--- /dev/null
+++ b/src/Tailviewer/BusinessLogic/DataSources/Custom/NoThrowCustomDataSourceConfiguration.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Reflection;
+using System.Xml;
+using log4net;
+using Tailviewer.BusinessLogic.Plugins;
+
+namespace Tailviewer.BusinessLogic.DataSources.Custom
+{
+ public class NoThrowCustomDataSourceConfiguration
+ : ICustomDataSourceConfiguration
+ {
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ public readonly ICustomDataSourceConfiguration Inner;
+
+ public NoThrowCustomDataSourceConfiguration(ICustomDataSourcePlugin inner, IServiceContainer serviceContainer)
+ {
+ try
+ {
+ Inner = inner.CreateConfiguration(serviceContainer);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ }
+ }
+
+ private NoThrowCustomDataSourceConfiguration(ICustomDataSourceConfiguration clone)
+ {
+ Inner = clone;
+ }
+
+ #region Implementation of ICloneable
+
+ public object Clone()
+ {
+ try
+ {
+ var clone = (ICustomDataSourceConfiguration)Inner?.Clone();
+ return new NoThrowCustomDataSourceConfiguration(clone);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ return new NoThrowCustomDataSourceConfiguration(null);
+ }
+ }
+
+ #endregion
+
+ #region Implementation of ICustomDataSourceConfiguration
+
+ public void Restore(XmlReader reader)
+ {
+ try
+ {
+ Inner?.Restore(reader);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ }
+ }
+
+ public void Store(XmlWriter writer)
+ {
+ try
+ {
+ Inner?.Store(writer);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/BusinessLogic/DataSources/Custom/NoThrowCustomDataSourcePlugin.cs b/src/Tailviewer/BusinessLogic/DataSources/Custom/NoThrowCustomDataSourcePlugin.cs
new file mode 100644
index 000000000..924585efb
--- /dev/null
+++ b/src/Tailviewer/BusinessLogic/DataSources/Custom/NoThrowCustomDataSourcePlugin.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Reflection;
+using System.Windows;
+using log4net;
+using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.BusinessLogic.Plugins;
+using Tailviewer.Core.LogFiles;
+using Tailviewer.Ui;
+
+namespace Tailviewer.BusinessLogic.DataSources.Custom
+{
+ public sealed class NoThrowCustomDataSourcePlugin
+ : ICustomDataSourcePlugin
+ {
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ private readonly string _name;
+ private readonly ICustomDataSourcePlugin _inner;
+ private readonly CustomDataSourceId _id;
+
+ public NoThrowCustomDataSourcePlugin(ICustomDataSourcePlugin inner)
+ {
+ _inner = inner;
+
+ try
+ {
+ _name = inner.DisplayName;
+ _id = inner.Id;
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ _name = inner.GetType().Name;
+ _id = null;
+ }
+ }
+
+ #region Implementation of ICustomDataSourcePlugin
+
+ public string DisplayName => _name;
+
+ public CustomDataSourceId Id => _id;
+
+ public ICustomDataSourceConfiguration CreateConfiguration(IServiceContainer serviceContainer)
+ {
+ return new NoThrowCustomDataSourceConfiguration(_inner, serviceContainer);
+ }
+
+ public ICustomDataSourceViewModel CreateViewModel(IServiceContainer serviceContainer,
+ ICustomDataSourceConfiguration configuration)
+ {
+ var actualConfig = ((NoThrowCustomDataSourceConfiguration) configuration).Inner;
+ try
+ {
+ return _inner.CreateViewModel(serviceContainer, actualConfig);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ return null;
+ }
+ }
+
+ public FrameworkElement CreateConfigurationControl(IServiceContainer serviceContainer, ICustomDataSourceViewModel viewModel)
+ {
+ try
+ {
+ return _inner.CreateConfigurationControl(serviceContainer, viewModel);
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ return null;
+ }
+ }
+
+ public ILogFile CreateLogFile(IServiceContainer serviceContainer, ICustomDataSourceConfiguration configuration)
+ {
+ var actualConfig = ((NoThrowCustomDataSourceConfiguration) configuration).Inner;
+ try
+ {
+ var actualLogFile = _inner.CreateLogFile(serviceContainer, actualConfig);
+ return new NoThrowLogFile(actualLogFile, "TODO");
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught unexpected exception: {0}", e);
+ return new EmptyLogFile();
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/BusinessLogic/DataSources/DataSources.cs b/src/Tailviewer/BusinessLogic/DataSources/DataSources.cs
index d527c93d4..1bec470cb 100644
--- a/src/Tailviewer/BusinessLogic/DataSources/DataSources.cs
+++ b/src/Tailviewer/BusinessLogic/DataSources/DataSources.cs
@@ -7,7 +7,9 @@
using Tailviewer.Settings;
using log4net;
using Tailviewer.BusinessLogic.Bookmarks;
+using Tailviewer.BusinessLogic.DataSources.Custom;
using Tailviewer.BusinessLogic.LogFiles;
+using Tailviewer.BusinessLogic.Plugins;
using Tailviewer.Settings.Bookmarks;
using Tailviewer.Settings.CustomFormats;
@@ -32,7 +34,7 @@ public sealed class DataSources
public DataSources(ILogFileFactory logFileFactory,
ITaskScheduler taskScheduler,
- IFilesystem filesystem,
+ IFilesystem filesystem,
IDataSourcesSettings settings,
IBookmarks bookmarks)
{
@@ -104,6 +106,11 @@ public void RemoveBookmark(Bookmark bookmark)
_bookmarks.RemoveBookmark(bookmark);
}
+ public IReadOnlyList CustomDataSources
+ {
+ get { return _logFileFactory.CustomDataSources; }
+ }
+
public bool Contains(DataSourceId id)
{
return _dataSourceIds.Contains(id);
@@ -155,7 +162,11 @@ private IDataSource AddDataSource(DataSource settings)
}
else if (!string.IsNullOrEmpty(settings.File))
{
- dataSource = new SingleDataSource(_logFileFactory, _taskScheduler, settings, _maximumWaitTime);
+ dataSource = new FileDataSource(_logFileFactory, _taskScheduler, settings, _maximumWaitTime);
+ }
+ else if (settings.CustomDataSourceConfiguration != null)
+ {
+ dataSource = new CustomDataSource(_logFileFactory, _taskScheduler, settings, _maximumWaitTime);
}
else
{
@@ -190,15 +201,37 @@ public MergedDataSource AddGroup()
return dataSource;
}
- public SingleDataSource AddFile(string fileName)
+ public CustomDataSource AddCustom(CustomDataSourceId id)
+ {
+ CustomDataSource dataSource;
+
+ var plugin = _logFileFactory.CustomDataSources.First(x => x.Id == id);
+
+ lock (_syncRoot)
+ {
+ var settings = new DataSource
+ {
+ Id = DataSourceId.CreateNew(),
+ DisplayName = plugin.DisplayName,
+ CustomDataSourceId = plugin.Id,
+ CustomDataSourceConfiguration = plugin.CreateConfiguration(null)
+ };
+ _settings.Add(settings);
+ dataSource = (CustomDataSource) AddDataSource(settings);
+ }
+
+ return dataSource;
+ }
+
+ public FileDataSource AddFile(string fileName)
{
string key = GetKey(fileName, out var fullFileName);
- SingleDataSource dataSource;
+ FileDataSource dataSource;
lock (_syncRoot)
{
dataSource =
- (SingleDataSource)
+ (FileDataSource)
_dataSources.FirstOrDefault(x => string.Equals(x.FullFileName, key, StringComparison.InvariantCultureIgnoreCase));
if (dataSource == null)
{
@@ -207,7 +240,7 @@ public SingleDataSource AddFile(string fileName)
Id = DataSourceId.CreateNew()
};
_settings.Add(settings);
- dataSource = (SingleDataSource) AddDataSource(settings);
+ dataSource = (FileDataSource) AddDataSource(settings);
}
}
diff --git a/src/Tailviewer/BusinessLogic/DataSources/SingleDataSource.cs b/src/Tailviewer/BusinessLogic/DataSources/FileDataSource.cs
similarity index 84%
rename from src/Tailviewer/BusinessLogic/DataSources/SingleDataSource.cs
rename to src/Tailviewer/BusinessLogic/DataSources/FileDataSource.cs
index 6f482c098..33d0c6cea 100644
--- a/src/Tailviewer/BusinessLogic/DataSources/SingleDataSource.cs
+++ b/src/Tailviewer/BusinessLogic/DataSources/FileDataSource.cs
@@ -7,21 +7,21 @@
namespace Tailviewer.BusinessLogic.DataSources
{
- public sealed class SingleDataSource
+ public sealed class FileDataSource
: AbstractDataSource
- , ISingleDataSource
+ , IFileDataSource
{
private readonly ILogFile _originalLogFile;
private readonly LogFileProxy _unfilteredLogFile;
private readonly IPluginDescription _pluginDescription;
private MultiLineLogFile _multiLineLogFile;
- public SingleDataSource(ILogFileFactory logFileFactory, ITaskScheduler taskScheduler, DataSource settings)
+ public FileDataSource(ILogFileFactory logFileFactory, ITaskScheduler taskScheduler, DataSource settings)
: this(logFileFactory, taskScheduler, settings, TimeSpan.FromMilliseconds(value: 10))
{
}
- public SingleDataSource(ILogFileFactory logFileFactory, ITaskScheduler taskScheduler, DataSource settings,
+ public FileDataSource(ILogFileFactory logFileFactory, ITaskScheduler taskScheduler, DataSource settings,
TimeSpan maximumWaitTime)
: base(taskScheduler, settings, maximumWaitTime)
{
@@ -34,7 +34,7 @@ public SingleDataSource(ILogFileFactory logFileFactory, ITaskScheduler taskSched
OnUnfilteredLogFileChanged();
}
- public SingleDataSource(ITaskScheduler taskScheduler, DataSource settings, ILogFile unfilteredLogFile,
+ public FileDataSource(ITaskScheduler taskScheduler, DataSource settings, ILogFile unfilteredLogFile,
TimeSpan maximumWaitTime)
: base(taskScheduler, settings, maximumWaitTime)
{
diff --git a/src/Tailviewer/BusinessLogic/DataSources/FolderDataSource.cs b/src/Tailviewer/BusinessLogic/DataSources/FolderDataSource.cs
index 97bbb1687..a2b608991 100644
--- a/src/Tailviewer/BusinessLogic/DataSources/FolderDataSource.cs
+++ b/src/Tailviewer/BusinessLogic/DataSources/FolderDataSource.cs
@@ -35,7 +35,7 @@ public sealed class FolderDataSource
private const char PatternSeparator = ';';
- private readonly Dictionary _dataSources;
+ private readonly Dictionary _dataSources;
private readonly MergedDataSource _mergedDataSource;
private readonly IFilesystem _filesystem;
private readonly ITaskScheduler _taskScheduler;
@@ -69,7 +69,7 @@ public FolderDataSource(ITaskScheduler taskScheduler,
_filesystem = filesystem;
_settings = settings;
_syncRoot = new object();
- _dataSources = new Dictionary();
+ _dataSources = new Dictionary();
_mergedDataSource = new MergedDataSource(taskScheduler, settings, maximumWaitTime);
_unfilteredLogFileProxy = new LogFileProxy(taskScheduler, maximumWaitTime);
_filteredLogFileProxy = new LogFileProxy(taskScheduler, maximumWaitTime);
@@ -488,7 +488,7 @@ private IReadOnlyList SynchronizeDataSources(IReadOnlyList Sources { get; }
+ IReadOnlyList CustomDataSources { get; }
+
///
/// Tests if a data source with the given id exists.
///
@@ -36,9 +40,12 @@ public interface IDataSources
///
bool Contains(DataSourceId id);
- SingleDataSource AddFile(string fileName);
+ FileDataSource AddFile(string fileName);
IFolderDataSource AddFolder(string folderPath);
MergedDataSource AddGroup();
+
+ CustomDataSource AddCustom(CustomDataSourceId id);
+
bool Remove(IDataSource viewModelDataSource);
#endregion
diff --git a/src/Tailviewer/BusinessLogic/DataSources/IFileDataSource.cs b/src/Tailviewer/BusinessLogic/DataSources/IFileDataSource.cs
new file mode 100644
index 000000000..a3c205d7e
--- /dev/null
+++ b/src/Tailviewer/BusinessLogic/DataSources/IFileDataSource.cs
@@ -0,0 +1,11 @@
+namespace Tailviewer.BusinessLogic.DataSources
+{
+ ///
+ ///
+ ///
+ public interface IFileDataSource
+ : ISingleDataSource
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/BusinessLogic/DataSources/ISingleDataSource.cs b/src/Tailviewer/BusinessLogic/DataSources/ISingleDataSource.cs
index 6b130fd56..021462d43 100644
--- a/src/Tailviewer/BusinessLogic/DataSources/ISingleDataSource.cs
+++ b/src/Tailviewer/BusinessLogic/DataSources/ISingleDataSource.cs
@@ -1,8 +1,12 @@
namespace Tailviewer.BusinessLogic.DataSources
{
///
- /// Tag interface for a data source which consists of most definitely only one source.
+ /// Tag interface for a data source which consists of a single, unspecified source.
///
+ ///
+ /// A class implementing this interface may additionally implement other tag interfaces to specify which
+ /// kind of data source it is.
+ ///
public interface ISingleDataSource
: IDataSource
{
diff --git a/src/Tailviewer/BusinessLogic/LogFiles/ILogFileFactory.cs b/src/Tailviewer/BusinessLogic/LogFiles/ILogFileFactory.cs
index 85d45504a..5e4aeacd3 100644
--- a/src/Tailviewer/BusinessLogic/LogFiles/ILogFileFactory.cs
+++ b/src/Tailviewer/BusinessLogic/LogFiles/ILogFileFactory.cs
@@ -1,4 +1,6 @@
+using System.Collections.Generic;
using Tailviewer.Archiver.Plugins.Description;
+using Tailviewer.BusinessLogic.Plugins;
namespace Tailviewer.BusinessLogic.LogFiles
{
@@ -14,5 +16,13 @@ public interface ILogFileFactory
///
///
ILogFile Open(string filePath, out IPluginDescription pluginDescription);
+
+ ///
+ ///
+ ///
+ IReadOnlyList CustomDataSources { get; }
+
+ ILogFile CreateCustom(CustomDataSourceId id, ICustomDataSourceConfiguration configuration,
+ out IPluginDescription pluginDescription);
}
}
\ No newline at end of file
diff --git a/src/Tailviewer/BusinessLogic/LogFiles/PluginLogFileFactory.cs b/src/Tailviewer/BusinessLogic/LogFiles/PluginLogFileFactory.cs
index 3dedd2267..64c90fe4b 100644
--- a/src/Tailviewer/BusinessLogic/LogFiles/PluginLogFileFactory.cs
+++ b/src/Tailviewer/BusinessLogic/LogFiles/PluginLogFileFactory.cs
@@ -22,9 +22,12 @@ public class PluginLogFileFactory
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly IReadOnlyList> _plugins;
+ private readonly IReadOnlyList> _dataSourcePlugins;
private readonly IServiceContainer _services;
- public PluginLogFileFactory(IServiceContainer services, IPluginWithDescription[] plugins)
+ public PluginLogFileFactory(IServiceContainer services,
+ IPluginWithDescription[] plugins,
+ IPluginWithDescription[] dataSourcePlugins)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
@@ -32,11 +35,16 @@ public PluginLogFileFactory(IServiceContainer services, IPluginWithDescription>(plugins);
+ _dataSourcePlugins = new List>(dataSourcePlugins);
_services = services;
}
- public PluginLogFileFactory(IServiceContainer services, IEnumerable> plugins)
- : this(services, plugins?.ToArray())
+ public PluginLogFileFactory(IServiceContainer services,
+ IEnumerable> plugins,
+ IEnumerable> dataSourcePlugins)
+ : this(services,
+ plugins?.ToArray() ?? new IPluginWithDescription[0],
+ dataSourcePlugins?.ToArray() ?? new IPluginWithDescription[0])
{}
///
@@ -56,6 +64,34 @@ public ILogFile Open(string filePath, out IPluginDescription pluginDescription)
return _services.CreateTextLogFile(filePath);
}
+ public IReadOnlyList CustomDataSources
+ {
+ get { return _dataSourcePlugins.Select(x => x.Plugin).ToList(); }
+ }
+
+ public ILogFile CreateCustom(CustomDataSourceId id, ICustomDataSourceConfiguration configuration,
+ out IPluginDescription pluginDescription)
+ {
+ var pair = _dataSourcePlugins.First(x => x.Plugin.Id == id);
+ pluginDescription = pair.Description;
+ var logFile = TryCreateCustomWith(pair.Plugin, configuration);
+ return new NoThrowLogFile(logFile, pluginDescription.Name);
+ }
+
+ private ILogFile TryCreateCustomWith(ICustomDataSourcePlugin plugin, ICustomDataSourceConfiguration configuration)
+ {
+ try
+ {
+ var logFile = plugin.CreateLogFile(_services, configuration);
+ return logFile;
+ }
+ catch (Exception e)
+ {
+ Log.ErrorFormat("Caught exception while trying to create custom log file: {0}", e);
+ return null;
+ }
+ }
+
private IFileFormatPlugin FindSupportingPlugin(string filePath, out IPluginDescription pluginDescription)
{
var fileName = Path.GetFileName(filePath);
diff --git a/src/Tailviewer/Settings/DataSource.cs b/src/Tailviewer/Settings/DataSource.cs
index a75c6b520..fddef0d89 100644
--- a/src/Tailviewer/Settings/DataSource.cs
+++ b/src/Tailviewer/Settings/DataSource.cs
@@ -6,6 +6,7 @@
using Tailviewer.BusinessLogic;
using log4net;
using Metrolib;
+using Tailviewer.BusinessLogic.Plugins;
using Tailviewer.Core;
namespace Tailviewer.Settings
@@ -82,6 +83,16 @@ public sealed class DataSource
public List ActivatedQuickFilters => _activatedQuickFilters;
+ ///
+ ///
+ ///
+ public CustomDataSourceId CustomDataSourceId;
+
+ ///
+ ///
+ ///
+ public ICustomDataSourceConfiguration CustomDataSourceConfiguration;
+
///
/// A user defined name for this data source.
///
@@ -111,6 +122,9 @@ public DataSource(string file)
public override string ToString()
{
+ if (CustomDataSourceConfiguration != null)
+ return string.Format("Custom ({0})", CustomDataSourceConfiguration);
+
if (File == null)
return string.Format("Merged ({0})", Id);
diff --git a/src/Tailviewer/Tailviewer.csproj b/src/Tailviewer/Tailviewer.csproj
index c89b08932..8368a29d1 100644
--- a/src/Tailviewer/Tailviewer.csproj
+++ b/src/Tailviewer/Tailviewer.csproj
@@ -105,10 +105,15 @@
+
+
+
+
+
@@ -302,6 +307,7 @@
+
@@ -347,9 +353,12 @@
+
+
+
@@ -363,10 +372,10 @@
-
+
-
+
Designer
@@ -380,6 +389,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -496,7 +509,7 @@
Designer
MSBuild:Compile
-
+
Designer
MSBuild:Compile
diff --git a/src/Tailviewer/Ui/Controls/DataSourceTree/CustomDataSourceTemplate.xaml b/src/Tailviewer/Ui/Controls/DataSourceTree/CustomDataSourceTemplate.xaml
new file mode 100644
index 000000000..60e44613b
--- /dev/null
+++ b/src/Tailviewer/Ui/Controls/DataSourceTree/CustomDataSourceTemplate.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.cs b/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.cs
index 0305fb806..3545e44e8 100644
--- a/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.cs
+++ b/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.cs
@@ -52,6 +52,12 @@ private static readonly DependencyPropertyKey FilteredItemsSourcePropertyKey
public static readonly DependencyProperty AddDataSourceFromFolderCommandProperty = DependencyProperty.Register(
"AddDataSourceFromFolderCommand", typeof(ICommand), typeof(DataSourcesControl), new PropertyMetadata(default(ICommand)));
+ public static readonly DependencyProperty AddCustomDataSourceCommandProperty = DependencyProperty.Register(
+ "AddCustomDataSourceCommand", typeof(ICommand), typeof(DataSourcesControl), new PropertyMetadata(default(ICommand)));
+
+ public static readonly DependencyProperty CustomDataSourcesProperty = DependencyProperty.Register(
+ "CustomDataSources", typeof(IEnumerable), typeof(DataSourcesControl), new PropertyMetadata(default(IEnumerable)));
+
public static DataSourcesControl Instance;
private FilterTextBox _partDataSourceSearch;
@@ -81,6 +87,18 @@ public ICommand AddDataSourceFromFolderCommand
set { SetValue(AddDataSourceFromFolderCommandProperty, value); }
}
+ public ICommand AddCustomDataSourceCommand
+ {
+ get { return (ICommand) GetValue(AddCustomDataSourceCommandProperty); }
+ set { SetValue(AddCustomDataSourceCommandProperty, value); }
+ }
+
+ public IEnumerable CustomDataSources
+ {
+ get { return (IEnumerable) GetValue(CustomDataSourcesProperty); }
+ set { SetValue(CustomDataSourcesProperty, value); }
+ }
+
public string StringFilter
{
get { return (string) GetValue(StringFilterProperty); }
@@ -198,7 +216,7 @@ public override void OnApplyTemplate()
private bool IsValidDrop(DragEventArgs e, out DropInfo dropInfo)
{
dropInfo = null;
- IDataSourceViewModel viewModel = e.Data.GetData(typeof (SingleDataSourceViewModel)) as IDataSourceViewModel ??
+ IDataSourceViewModel viewModel = e.Data.GetData(typeof (FileDataSourceViewModel)) as IDataSourceViewModel ??
e.Data.GetData(typeof (MergedDataSourceViewModel)) as IDataSourceViewModel;
var source = new TreeItem
{
@@ -242,7 +260,7 @@ private DataSourceDropType GetDropType(DragEventArgs e,
var dropType = DataSourceDropType.None;
double height = destination.TreeViewItem.ActualHeight;
- if (source is SingleDataSourceViewModel)
+ if (source is FileDataSourceViewModel)
{
// Let's distribute it as follows:
// 20% top => arrange
diff --git a/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.xaml b/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.xaml
index 168be9750..55b3ef434 100644
--- a/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.xaml
+++ b/src/Tailviewer/Ui/Controls/DataSourceTree/DataSourcesControl.xaml
@@ -9,7 +9,9 @@
+ Source="pack://application:,,,/Tailviewer;component/Ui/Controls/DataSourceTree/CustomDataSourceTemplate.xaml" />
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/Tailviewer/Ui/Controls/MainPanel/LogViewMainPanelViewModel.cs b/src/Tailviewer/Ui/Controls/MainPanel/LogViewMainPanelViewModel.cs
index de008cd0f..8d77de1e3 100644
--- a/src/Tailviewer/Ui/Controls/MainPanel/LogViewMainPanelViewModel.cs
+++ b/src/Tailviewer/Ui/Controls/MainPanel/LogViewMainPanelViewModel.cs
@@ -9,6 +9,7 @@
using Tailviewer.BusinessLogic.ActionCenter;
using Tailviewer.BusinessLogic.Bookmarks;
using Tailviewer.BusinessLogic.DataSources;
+using Tailviewer.BusinessLogic.DataSources.Custom;
using Tailviewer.BusinessLogic.Filters;
using Tailviewer.BusinessLogic.Highlighters;
using Tailviewer.Settings;
diff --git a/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesDataTemplate.xaml b/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesDataTemplate.xaml
index 82c2bc4fb..0d86b123d 100644
--- a/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesDataTemplate.xaml
+++ b/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesDataTemplate.xaml
@@ -9,6 +9,7 @@
ItemsSource="{Binding Observable}"
AddDataSourceFromFileCommand="{Binding AddDataSourceFromFileCommand}"
AddDataSourceFromFolderCommand="{Binding AddDataSourceFromFolderCommand}"
+ CustomDataSources="{Binding CustomDataSources}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
diff --git a/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesViewModel.cs b/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesViewModel.cs
index 7855408b0..f497ce245 100644
--- a/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesViewModel.cs
+++ b/src/Tailviewer/Ui/Controls/SidePanel/DataSources/DataSourcesViewModel.cs
@@ -13,6 +13,8 @@
using Tailviewer.BusinessLogic;
using Tailviewer.BusinessLogic.ActionCenter;
using Tailviewer.BusinessLogic.DataSources;
+using Tailviewer.BusinessLogic.DataSources.Custom;
+using Tailviewer.BusinessLogic.Plugins;
using Tailviewer.Settings;
using Tailviewer.Ui.Controls.DataSourceTree;
using Tailviewer.Ui.ViewModels;
@@ -29,10 +31,13 @@ public sealed class DataSourcesViewModel
private readonly IApplicationSettings _settings;
private readonly ICommand _addDataSourceFromFileCommand;
private readonly ICommand _addDataSourceFromFolderCommand;
+ private readonly IReadOnlyList _customDataSources;
private IDataSourceViewModel _selectedItem;
- public DataSourcesViewModel(IApplicationSettings settings, IDataSources dataSources, IActionCenter actionCenter)
+ public DataSourcesViewModel(IApplicationSettings settings,
+ IDataSources dataSources,
+ IActionCenter actionCenter)
{
if (settings == null) throw new ArgumentNullException(nameof(settings));
if (dataSources == null) throw new ArgumentNullException(nameof(dataSources));
@@ -65,10 +70,19 @@ public DataSourcesViewModel(IApplicationSettings settings, IDataSources dataSour
}
}
+ _customDataSources =
+ _dataSources.CustomDataSources.Select(x => new AddCustomDataSourceViewModel(x.DisplayName, () => AddCustomDataSource(x.Id))).ToList();
+
UpdateTooltip();
PropertyChanged += OnPropertyChanged;
}
+ private void AddCustomDataSource(CustomDataSourceId id)
+ {
+ var dataSource = _dataSources.AddCustom(id);
+ Add(dataSource);
+ }
+
private void UpdateTooltip()
{
Tooltip = IsSelected
@@ -90,6 +104,8 @@ private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
public ICommand AddDataSourceFromFolderCommand => _addDataSourceFromFolderCommand;
+ public IEnumerable CustomDataSources => _customDataSources;
+
public IDataSourceViewModel SelectedItem
{
get { return _selectedItem; }
@@ -220,7 +236,7 @@ private void AddDataSourceFromFolder()
private bool Represents(IDataSourceViewModel dataSourceViewModel, string fullName)
{
- var file = dataSourceViewModel as SingleDataSourceViewModel;
+ var file = dataSourceViewModel as FileDataSourceViewModel;
if (file == null)
return false;
@@ -240,31 +256,27 @@ private IDataSourceViewModel CreateViewModel(IDataSource dataSource)
throw new ArgumentNullException(nameof(dataSource));
IDataSourceViewModel viewModel;
- var single = dataSource as ISingleDataSource;
- if (single != null)
+ if (dataSource is IFileDataSource single)
+ {
+ viewModel = new FileDataSourceViewModel(single, _actionCenter);
+ }
+ else if (dataSource is IMergedDataSource merged)
+ {
+ viewModel = new MergedDataSourceViewModel(merged, _actionCenter);
+ }
+ else if (dataSource is IFolderDataSource folder)
+ {
+ viewModel = new FolderDataSourceViewModel(folder, _actionCenter);
+ }
+ else if (dataSource is ICustomDataSource custom)
{
- viewModel = new SingleDataSourceViewModel(single, _actionCenter);
+ viewModel = new CustomDataSourceViewModel(custom);
}
else
{
- var merged = dataSource as IMergedDataSource;
- if (merged != null)
- {
- viewModel = new MergedDataSourceViewModel(merged, _actionCenter);
- }
- else
- {
- var folder = dataSource as IFolderDataSource;
- if (folder != null)
- {
- viewModel = new FolderDataSourceViewModel(folder, _actionCenter);
- }
- else
- {
- throw new ArgumentException(string.Format("Unknown data source: {0} ({1})", dataSource, dataSource.GetType()));
- }
- }
+ throw new ArgumentException(string.Format("Unknown data source: {0} ({1})", dataSource, dataSource.GetType()));
}
+
viewModel.Remove += OnRemove;
_allDataSourceViewModels.Add(viewModel);
diff --git a/src/Tailviewer/Ui/ViewModels/AddCustomDataSourceViewModel.cs b/src/Tailviewer/Ui/ViewModels/AddCustomDataSourceViewModel.cs
new file mode 100644
index 000000000..815f0caf3
--- /dev/null
+++ b/src/Tailviewer/Ui/ViewModels/AddCustomDataSourceViewModel.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Windows.Input;
+using Metrolib;
+
+namespace Tailviewer.Ui.ViewModels
+{
+ ///
+ ///
+ ///
+ public sealed class AddCustomDataSourceViewModel
+ {
+ private readonly ICommand _command;
+ private readonly string _name;
+
+ public ICommand AddCommand => _command;
+
+ public string Name => _name;
+
+ public AddCustomDataSourceViewModel(string name, Action add)
+ {
+ _name = name;
+ _command = new DelegateCommand2(add);
+ }
+ }
+}
diff --git a/src/Tailviewer/Ui/ViewModels/CustomDataSourceViewModel.cs b/src/Tailviewer/Ui/ViewModels/CustomDataSourceViewModel.cs
new file mode 100644
index 000000000..85e0bfa0c
--- /dev/null
+++ b/src/Tailviewer/Ui/ViewModels/CustomDataSourceViewModel.cs
@@ -0,0 +1,60 @@
+using System.Windows.Input;
+using Tailviewer.BusinessLogic.DataSources;
+
+namespace Tailviewer.Ui.ViewModels
+{
+ public sealed class CustomDataSourceViewModel
+ : AbstractDataSourceViewModel
+ , ISingleDataSourceViewModel
+ {
+ public CustomDataSourceViewModel(IDataSource dataSource) : base(dataSource)
+ {
+ }
+
+ #region Overrides of AbstractDataSourceViewModel
+
+ public override ICommand OpenInExplorerCommand
+ {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ public override string DisplayName
+ {
+ get { throw new System.NotImplementedException(); }
+ set { throw new System.NotImplementedException(); }
+ }
+
+ public override bool CanBeRenamed
+ {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ public override string DataSourceOrigin
+ {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ #endregion
+
+ #region Implementation of ISingleDataSourceViewModel
+
+ public bool CanBeRemoved
+ {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ public string CharacterCode
+ {
+ get { throw new System.NotImplementedException(); }
+ set { throw new System.NotImplementedException(); }
+ }
+
+ public bool ExcludeFromParent
+ {
+ get { throw new System.NotImplementedException(); }
+ set { throw new System.NotImplementedException(); }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/Ui/ViewModels/SingleDataSourceViewModel.cs b/src/Tailviewer/Ui/ViewModels/FileDataSourceViewModel.cs
similarity index 97%
rename from src/Tailviewer/Ui/ViewModels/SingleDataSourceViewModel.cs
rename to src/Tailviewer/Ui/ViewModels/FileDataSourceViewModel.cs
index f09eb2cee..bdeebeba6 100644
--- a/src/Tailviewer/Ui/ViewModels/SingleDataSourceViewModel.cs
+++ b/src/Tailviewer/Ui/ViewModels/FileDataSourceViewModel.cs
@@ -14,9 +14,9 @@ namespace Tailviewer.Ui.ViewModels
///
/// Represents a data source and is capable of opening the source folder in explorer
///
- public sealed class SingleDataSourceViewModel
+ public sealed class FileDataSourceViewModel
: AbstractDataSourceViewModel
- , ISingleDataSourceViewModel
+ , IFileDataSourceViewModel
{
private readonly IActionCenter _actionCenter;
private readonly ISingleDataSource _dataSource;
@@ -27,7 +27,7 @@ public sealed class SingleDataSourceViewModel
private bool _canBeRemoved;
private bool _excludeFromParent;
- public SingleDataSourceViewModel(ISingleDataSource dataSource,
+ public FileDataSourceViewModel(IFileDataSource dataSource,
IActionCenter actionCenter)
: base(dataSource)
{
diff --git a/src/Tailviewer/Ui/ViewModels/FolderDataSourceViewModel.cs b/src/Tailviewer/Ui/ViewModels/FolderDataSourceViewModel.cs
index 9f07eef51..bc2b63c18 100644
--- a/src/Tailviewer/Ui/ViewModels/FolderDataSourceViewModel.cs
+++ b/src/Tailviewer/Ui/ViewModels/FolderDataSourceViewModel.cs
@@ -199,7 +199,7 @@ private bool AddNewDataSources(IReadOnlyList dataSources)
{
if (!_dataSourceViewModelsByDataSource.TryGetValue(dataSource, out var viewModel))
{
- viewModel = new SingleDataSourceViewModel((ISingleDataSource) dataSource, _actionCenter)
+ viewModel = new FileDataSourceViewModel((IFileDataSource) dataSource, _actionCenter)
{
Parent = this
};
diff --git a/src/Tailviewer/Ui/ViewModels/ICustomDataSourceViewModel.cs b/src/Tailviewer/Ui/ViewModels/ICustomDataSourceViewModel.cs
new file mode 100644
index 000000000..3228c6a9a
--- /dev/null
+++ b/src/Tailviewer/Ui/ViewModels/ICustomDataSourceViewModel.cs
@@ -0,0 +1,8 @@
+namespace Tailviewer.Ui.ViewModels
+{
+ public interface ICustomDataSourceViewModel
+ : ISingleDataSourceViewModel
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/Ui/ViewModels/IFileDataSourceViewModel.cs b/src/Tailviewer/Ui/ViewModels/IFileDataSourceViewModel.cs
new file mode 100644
index 000000000..63b417fcf
--- /dev/null
+++ b/src/Tailviewer/Ui/ViewModels/IFileDataSourceViewModel.cs
@@ -0,0 +1,6 @@
+namespace Tailviewer.Ui.ViewModels
+{
+ public interface IFileDataSourceViewModel
+ : ISingleDataSourceViewModel
+ { }
+}
\ No newline at end of file
diff --git a/src/Tailviewer/Ui/ViewModels/MainWindowViewModel.cs b/src/Tailviewer/Ui/ViewModels/MainWindowViewModel.cs
index cdd265e78..b3dcef7f7 100644
--- a/src/Tailviewer/Ui/ViewModels/MainWindowViewModel.cs
+++ b/src/Tailviewer/Ui/ViewModels/MainWindowViewModel.cs
@@ -10,6 +10,8 @@
using Tailviewer.Archiver.Plugins.Description;
using Tailviewer.BusinessLogic.ActionCenter;
using Tailviewer.BusinessLogic.AutoUpdates;
+using Tailviewer.BusinessLogic.DataSources;
+using Tailviewer.BusinessLogic.DataSources.Custom;
using Tailviewer.BusinessLogic.Highlighters;
using Tailviewer.BusinessLogic.Plugins;
using Tailviewer.Settings;