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;