diff --git a/ESCPOS_NET.ConsoleTest/Program.cs b/ESCPOS_NET.ConsoleTest/Program.cs index 4e6c60c..0ead6f8 100644 --- a/ESCPOS_NET.ConsoleTest/Program.cs +++ b/ESCPOS_NET.ConsoleTest/Program.cs @@ -12,10 +12,13 @@ internal class Program { private static BasePrinter printer; private static ICommandEmitter e; + /// + /// Indicate whether to test with long-lived printer object or create and dispose every time. + /// + private const bool SINGLETON_PRINTER_OBJECT = false; static void Main(string[] args) { - Console.WriteLine("Welcome to the ESCPOS_NET Test Application!"); Console.Write("Would you like to see all debug messages? (y/n): "); var response = Console.ReadLine().Trim().ToLowerInvariant(); @@ -34,8 +37,9 @@ static void Main(string[] args) Console.WriteLine("3 ) Test Samba-Shared Printer"); Console.Write("Choice: "); string comPort = ""; - string ip; - string networkPort; + string ip = ""; + string networkPort = ""; + Action createPrinter = null; string smbPath; response = Console.ReadLine(); var valid = new List { "1", "2", "3" }; @@ -64,7 +68,10 @@ static void Main(string[] args) { baudRate = 115200; } - printer = new SerialPrinter(portName: comPort, baudRate: baudRate); + if (SINGLETON_PRINTER_OBJECT) + printer = new SerialPrinter(portName: comPort, baudRate: baudRate); + else + createPrinter = () => { printer = new SerialPrinter(portName: comPort, baudRate: baudRate); }; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { @@ -74,7 +81,10 @@ static void Main(string[] args) { comPort = "/dev/usb/lp0"; } - printer = new FilePrinter(filePath: comPort, false); + if (SINGLETON_PRINTER_OBJECT) + printer = new FilePrinter(filePath: comPort, false); + else + createPrinter = () => { printer = new FilePrinter(filePath: comPort, false); }; } } else if (choice == 2) @@ -83,7 +93,7 @@ static void Main(string[] args) ip = Console.ReadLine(); if (string.IsNullOrWhiteSpace(ip)) { - ip = "192.168.254.202"; + ip = "127.0.0.1"; // default to local for using TCPPrintServerTest } Console.Write("TCP Port (enter for default 9100): "); networkPort = Console.ReadLine(); @@ -91,7 +101,10 @@ static void Main(string[] args) { networkPort = "9100"; } - printer = new NetworkPrinter(settings: new NetworkPrinterSettings() { ConnectionString = $"{ip}:{networkPort}" }); + if (SINGLETON_PRINTER_OBJECT) + printer = new NetworkPrinter(settings: new NetworkPrinterSettings() { ConnectionString = $"{ip}:{networkPort}" }); + else + createPrinter = () => { printer = new NetworkPrinter(settings: new NetworkPrinterSettings() { ConnectionString = $"{ip}:{networkPort}" }); }; } else if (choice == 3) { @@ -147,6 +160,11 @@ static void Main(string[] args) continue; } + if (!SINGLETON_PRINTER_OBJECT) + { + createPrinter(); + } + var enumChoice = (Option)choice; if (enumChoice == Option.Exit) { @@ -157,50 +175,50 @@ static void Main(string[] args) if (monitor) { - printer.Write(e.Initialize()); - printer.Write(e.Enable()); - printer.Write(e.EnableAutomaticStatusBack()); + printer.WriteTest(e.Initialize()); + printer.WriteTest(e.Enable()); + printer.WriteTest(e.EnableAutomaticStatusBack()); } Setup(monitor); - printer?.Write(e.PrintLine($"== [ Start {testCases[enumChoice]} ] ==")); + printer?.WriteTest(e.PrintLine($"== [ Start {testCases[enumChoice]} ] ==")); switch (enumChoice) { case Option.SingleLinePrinting: - printer.Write(Tests.SingleLinePrinting(e)); + printer.WriteTest(Tests.SingleLinePrinting(e)); break; case Option.MultiLinePrinting: - printer.Write(Tests.MultiLinePrinting(e)); + printer.WriteTest(Tests.MultiLinePrinting(e)); break; case Option.LineSpacing: - printer.Write(Tests.LineSpacing(e)); + printer.WriteTest(Tests.LineSpacing(e)); break; case Option.BarcodeStyles: - printer.Write(Tests.BarcodeStyles(e)); + printer.WriteTest(Tests.BarcodeStyles(e)); break; case Option.BarcodeTypes: - printer.Write(Tests.BarcodeTypes(e)); + printer.WriteTest(Tests.BarcodeTypes(e)); break; case Option.TwoDimensionCodes: - printer.Write(Tests.TwoDimensionCodes(e)); + printer.WriteTest(Tests.TwoDimensionCodes(e)); break; case Option.TextStyles: - printer.Write(Tests.TextStyles(e)); + printer.WriteTest(Tests.TextStyles(e)); break; case Option.FullReceipt: - printer.Write(Tests.Receipt(e)); + printer.WriteTest(Tests.Receipt(e)); break; case Option.Images: - printer.Write(Tests.Images(e, false)); + printer.WriteTest(Tests.Images(e, false)); break; case Option.LegacyImages: - printer.Write(Tests.Images(e, true)); + printer.WriteTest(Tests.Images(e, true)); break; case Option.LargeByteArrays: try { - printer.Write(Tests.TestLargeByteArrays(e)); + printer.WriteTest(Tests.TestLargeByteArrays(e)); } catch (Exception e) { @@ -208,10 +226,10 @@ static void Main(string[] args) } break; case Option.CashDrawerPin2: - printer.Write(Tests.CashDrawerOpenPin2(e)); + printer.WriteTest(Tests.CashDrawerOpenPin2(e)); break; case Option.CashDrawerPin5: - printer.Write(Tests.CashDrawerOpenPin5(e)); + printer.WriteTest(Tests.CashDrawerOpenPin5(e)); break; default: Console.WriteLine("Invalid entry."); @@ -219,9 +237,10 @@ static void Main(string[] args) } Setup(monitor); - printer?.Write(e.PrintLine($"== [ End {testCases[enumChoice]} ] ==")); - printer?.Write(e.PartialCutAfterFeed(5)); - + printer?.WriteTest(e.PrintLine($"== [ End {testCases[enumChoice]} ] ==")); + printer?.WriteTest(e.PartialCutAfterFeed(5)); + if (!SINGLETON_PRINTER_OBJECT) + printer?.Dispose(); // TODO: also make an automatic runner that runs all tests (command line). } } @@ -273,4 +292,19 @@ private static void Setup(bool enableStatusBackMonitoring) } } } + + internal static class TestExtensions + { + /// + /// Wrapper exception function for ease of switching between Write and WriteAsync function + /// + /// + /// + internal static void WriteTest(this BasePrinter printer, params byte[][] arrays) + { + // Switch to use this if need to test with obsolated Write function + //printer.Write(arrays); + printer.WriteAsync(arrays).Wait(); + } + } } diff --git a/ESCPOS_NET.ConsoleTest/TestLargeByteArrays.cs b/ESCPOS_NET.ConsoleTest/TestLargeByteArrays.cs index aa7b6a4..897fcbc 100644 --- a/ESCPOS_NET.ConsoleTest/TestLargeByteArrays.cs +++ b/ESCPOS_NET.ConsoleTest/TestLargeByteArrays.cs @@ -35,7 +35,7 @@ public static byte[] TestLargeByteArrays(ICommandEmitter e) cube ); MemoryPrinter mp = new MemoryPrinter(); - mp.Write(expectedResult); + mp.WriteTest(expectedResult); var response = mp.GetAllData(); bool hasErrors = false; if (expectedResult.Length != response.Length) @@ -68,7 +68,7 @@ public static byte[] TestLargeByteArrays(ICommandEmitter e) var filename = $"{r.NextDouble().ToString()}.tmp"; using (FilePrinter fp = new FilePrinter(filename, true)) { - fp.Write(expectedResult); + fp.WriteTest(expectedResult); } response = File.ReadAllBytes(filename); hasErrors = false; diff --git a/ESCPOS_NET.TCPPrintServerTest/Program.cs b/ESCPOS_NET.TCPPrintServerTest/Program.cs index 053ccce..fb75219 100644 --- a/ESCPOS_NET.TCPPrintServerTest/Program.cs +++ b/ESCPOS_NET.TCPPrintServerTest/Program.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.IO; using System.Text; +using System.Threading; namespace TcpEchoServer { @@ -18,23 +19,40 @@ public static void Main() int port = 9100; TcpListener listener = new TcpListener(IPAddress.Loopback, port); listener.Start(); + TcpClient client; - TcpClient client = listener.AcceptTcpClient(); - NetworkStream stream = client.GetStream(); + while (true) + { + // Accept multiple connections with a dedicated thread for each connection + client = listener.AcceptTcpClient(); + ThreadPool.QueueUserWorkItem(TcpClientConnectionHandler, client); + } + } + + private static void TcpClientConnectionHandler(object obj) + { + var tcp = (TcpClient)obj; + NetworkStream stream = tcp.GetStream(); StreamWriter writer = new StreamWriter(stream, Encoding.ASCII) { AutoFlush = true }; StreamReader reader = new StreamReader(stream, Encoding.ASCII); - - while (true) + try { - string inputLine = ""; - while (inputLine != null) + while (true) { - inputLine = reader.ReadLine(); - writer.Write("E"); - Console.WriteLine("Echoing string: " + inputLine); + string inputLine = ""; + while (inputLine != null) + { + inputLine = reader.ReadLine(); + writer.Write("E"); + Console.WriteLine("Echoing string: " + inputLine); + } + Console.WriteLine("Server saw disconnect from client."); } - Console.WriteLine("Server saw disconnect from client."); } + catch(IOException) + { + // connection is closed + } } } } diff --git a/ESCPOS_NET/ESCPOS_NET.csproj b/ESCPOS_NET/ESCPOS_NET.csproj index 1ed71d2..e442415 100644 --- a/ESCPOS_NET/ESCPOS_NET.csproj +++ b/ESCPOS_NET/ESCPOS_NET.csproj @@ -32,10 +32,10 @@ - + - + diff --git a/ESCPOS_NET/Printers/BasePrinter.cs b/ESCPOS_NET/Printers/BasePrinter.cs index 9b36e34..fca6215 100644 --- a/ESCPOS_NET/Printers/BasePrinter.cs +++ b/ESCPOS_NET/Printers/BasePrinter.cs @@ -1,6 +1,7 @@ using ESCPOS_NET.Utilities; using Microsoft.Extensions.Logging; using System; +using System.Collections; using System.Collections.Concurrent; using System.IO; using System.Linq; @@ -18,7 +19,6 @@ public abstract partial class BasePrinter : IPrinter, IDisposable //private volatile bool _isMonitoring; private CancellationTokenSource _readCancellationTokenSource; - private CancellationTokenSource _writeCancellationTokenSource; private readonly int _maxBytesPerWrite = 15000; // max byte chunks to write at once. @@ -29,19 +29,23 @@ public abstract partial class BasePrinter : IPrinter, IDisposable public event EventHandler Connected; protected BinaryWriter Writer { get; set; } - protected BinaryReader Reader { get; set; } protected ConcurrentQueue ReadBuffer { get; set; } = new ConcurrentQueue(); - - protected ConcurrentQueue WriteBuffer { get; set; } = new ConcurrentQueue(); + private readonly BlockingCollection<(byte[] bytes, TaskCompletionSource taskSource)> _writeBuffer = + new BlockingCollection<(byte[] bytes, TaskCompletionSource taskSource)>(); protected int BytesWrittenSinceLastFlush { get; set; } = 0; - protected volatile bool IsConnected = true; + protected virtual bool IsConnected { get; } = true; public string PrinterName { get; protected set; } + /// + /// Timeout in millisecond to wait for the connection to be connected when call WriteAsync function with await. Default is 5000 milliseconds. + /// + public int WriteTimeout { get; set; } = 5000; + protected BasePrinter() { PrinterName = Guid.NewGuid().ToString(); @@ -59,10 +63,9 @@ protected BasePrinter(string printerName) private void Init() { _readCancellationTokenSource = new CancellationTokenSource(); - _writeCancellationTokenSource = new CancellationTokenSource(); Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Initializing Task Threads...", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); //Task.Factory.StartNew(MonitorPrinterStatusLongRunningTask, _connectivityCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); - Task.Factory.StartNew(WriteLongRunningTask, _writeCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); + Task.Factory.StartNew(WriteLongRunningTask, TaskCreationOptions.LongRunning).ConfigureAwait(false); Task.Factory.StartNew(ReadLongRunningTask, _readCancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false); // TODO: read and status monitoring probably won't work for fileprinter, should let printer types disable this feature. Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Task Threads started", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); @@ -83,41 +86,60 @@ protected virtual void Reconnect() // Implemented in the network printer } protected virtual async void WriteLongRunningTask() - { - while (true) + { + // Loop when there is a new item in the _writeBuffer, break when _writeBuffer.CompleteAdding() is called (in the dispose) + foreach (var (nextBytes, taskSource) in _writeBuffer.GetConsumingEnumerable()) { - if (_writeCancellationTokenSource != null && _writeCancellationTokenSource.IsCancellationRequested) + var writeSuccess = false; + var isAwaitableWrite = taskSource != null; + do { - Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Write Long-Running Task Cancellation was requested.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); - break; - } - - await Task.Delay(100); - if (!IsConnected) - { - continue; - } + await Task.WhenAny( + Task.Delay(WriteTimeout), + Task.Run(async () => + { + while (!IsConnected) // Await for the connection to the printer get restored + { + await Task.Delay(100); + } + }) + ); - try - { - var didDequeue = WriteBuffer.TryDequeue(out var nextBytes); - if (didDequeue && nextBytes?.Length > 0) + if (!IsConnected) { - WriteToBinaryWriter(nextBytes); + taskSource?.SetException(new IOException($"Unrecoverable connectivity error writing to printer.")); + continue; + } + try + { + if (nextBytes?.Length > 0) + { + WriteToBinaryWriter(nextBytes); + taskSource?.SetResult(true); + } + else + { + taskSource?.SetResult(false); + } + writeSuccess = true; + } + catch (IOException ex) + { + // Thrown if the printer times out the socket connection + // default is 90 seconds + taskSource?.TrySetException(ex); + //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing IOException... sometimes happens with network printers. Should get reconnected automatically."); + } + catch (Exception ex) + { + taskSource?.TrySetException(ex); + //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing generic read exception... sometimes happens with serial port printers."); } } - catch (IOException) - { - // Thrown if the printer times out the socket connection - // default is 90 seconds - //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing IOException... sometimes happens with network printers. Should get reconnected automatically."); - } - catch - { - // Swallow the exception - //Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Swallowing generic read exception... sometimes happens with serial port printers."); - } + while (!isAwaitableWrite && !writeSuccess); } + + Logging.Logger?.LogDebug("[{Function}]:[{PrinterName}] Write Long-Running Task Cancellation was requested.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); } protected virtual async void ReadLongRunningTask() @@ -146,7 +168,6 @@ protected virtual async void ReadLongRunningTask() DataAvailable(); } } - catch { // Swallow the exception @@ -155,19 +176,34 @@ protected virtual async void ReadLongRunningTask() } } - public virtual void Write(params byte[][] arrays) + /// + public virtual void Write(params byte[][] byteArrays) { - Write(ByteSplicer.Combine(arrays)); + Write(ByteSplicer.Combine(byteArrays)); } + /// public virtual void Write(byte[] bytes) { - WriteBuffer.Enqueue(bytes); + _writeBuffer.Add((bytes, null)); } - protected virtual void WriteToBinaryWriter(byte[] bytes) + /// + public virtual async Task WriteAsync(params byte[][] byteArrays) { + await WriteAsync(ByteSplicer.Combine(byteArrays)); + } + /// + public virtual async Task WriteAsync(byte[] bytes) + { + var taskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _writeBuffer.Add((bytes, taskSource)); + await taskSource.Task; + } + + protected virtual void WriteToBinaryWriter(byte[] bytes) + { if (!IsConnected) { Logging.Logger?.LogInformation("[{Function}]:[{PrinterName}] Attempted to write but printer isn't connected. Attempting to reconnect...", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); @@ -185,7 +221,6 @@ protected virtual void WriteToBinaryWriter(byte[] bytes) bool hasFlushed = false; while (bytesLeft > 0) { - int count = Math.Min(_maxBytesPerWrite, bytesLeft); try { @@ -222,7 +257,6 @@ public virtual void Flush(object sender, ElapsedEventArgs e) { try { - BytesWrittenSinceLastFlush = 0; Writer.Flush(); } @@ -232,9 +266,9 @@ public virtual void Flush(object sender, ElapsedEventArgs e) } } - public virtual void DataAvailable() + private void DataAvailable() { - if (ReadBuffer.Count() % 4 == 0) + if (ReadBuffer.Count % 4 == 0) { var bytes = new byte[4]; for (int i = 0; i < 4; i++) @@ -321,6 +355,14 @@ protected virtual void Dispose(bool disposing) Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue during cancellation token cancellation call.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); } try + { + _writeBuffer.CompleteAdding(); + } + catch (ObjectDisposedException e) + { + Logging.Logger?.LogDebug(e, "[{Function}]:[{PrinterName}] Dispose Issue during closing write buffer.", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName); + } + try { Reader?.Close(); } diff --git a/ESCPOS_NET/Printers/IPrinter.cs b/ESCPOS_NET/Printers/IPrinter.cs index 82530d6..6bd596b 100644 --- a/ESCPOS_NET/Printers/IPrinter.cs +++ b/ESCPOS_NET/Printers/IPrinter.cs @@ -1,11 +1,55 @@ using System; +using System.Threading.Tasks; namespace ESCPOS_NET { public interface IPrinter { PrinterStatusEventArgs GetStatus(); - void Write(params byte[][] arrays); + /// + /// Write byte array of array to the printer stream. This function discards the and the parameter will still be written + /// to the printer stream when the connection restored if this instance is disposed yet + /// + /// Array of byte array which to be flattened to the overloaded function. + /// + void Write(params byte[][] byteArrays); + /// + /// Write byte array of array to the printer stream. This function discards the and the parameter will still be written + /// to the printer stream when the connection restored if this instance is disposed yet + /// + /// Byte array to write to the printer stream. + /// void Write(byte[] bytes); + /// + /// Write byte array of array to the printer stream. + /// + /// Array of byte array which to be flattened to the overloaded function. + /// + /// + /// await or Wait() this function to await the operation and it would properly capture the exception otherwise the exception will be swallowed. + /// Not await nor Wait() this function would discard the and the parameter will still be written + /// to the printer stream when the connection restored if this instance is disposed yet. + /// + /// The is reach or Attempt to write stream to the disconnected connection + Task WriteAsync(params byte[][] byteArrays); + /// + /// Write byte array of array to the printer stream. + /// + /// Byte array to write to the printer stream. + /// + /// + /// await or Wait() this function to await the operation and it would properly capture the exception otherwise the exception will be swallowed. + /// Not await nor Wait() this function would discard the and the parameter will still be written + /// to the printer stream when the connection restored if this instance is disposed yet. + /// + /// The is reach or Attempt to write stream to the disconnected connection + Task WriteAsync(byte[] bytes); + + event EventHandler StatusChanged; + event EventHandler Disconnected; + event EventHandler Connected; + //event EventHandler WriteFailed; + //event EventHandler Idle; + //event EventHandler IdleDisconnected; is this useful? to know that it disconnected because of idle? probably better to have this as info in disconnected event object instead. } } \ No newline at end of file diff --git a/ESCPOS_NET/Printers/NetworkPrinter.cs b/ESCPOS_NET/Printers/NetworkPrinter.cs index a1dba44..f650987 100644 --- a/ESCPOS_NET/Printers/NetworkPrinter.cs +++ b/ESCPOS_NET/Printers/NetworkPrinter.cs @@ -4,6 +4,7 @@ using System.IO; using System.Threading.Tasks; using System.Reflection; +using System.Net.Sockets; namespace ESCPOS_NET { @@ -26,6 +27,8 @@ public class NetworkPrinter : BasePrinter private readonly NetworkPrinterSettings _settings; private TCPConnection _tcpConnection; + protected override bool IsConnected => _tcpConnection?.IsConnected??false; + public NetworkPrinter(NetworkPrinterSettings settings) : base(settings.PrinterName) { _settings = settings; @@ -43,17 +46,20 @@ public NetworkPrinter(NetworkPrinterSettings settings) : base(settings.PrinterNa private void ConnectedEvent(object sender, ClientConnectedEventArgs e) { Logging.Logger?.LogInformation("[{Function}]:[{PrinterName}] Connected successfully to network printer! Connection String: {ConnectionString}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, _settings.ConnectionString); - IsConnected = true; + // Close previously created reader and writer if any (for handling reconnect after connection lose (in the future)) + Reader?.Close(); + Writer?.Close(); + Reader = new BinaryReader(_tcpConnection.ReadStream); + Writer = new BinaryWriter(_tcpConnection.WriteStream); InvokeConnect(); } private void DisconnectedEvent(object sender, ClientDisconnectedEventArgs e) { - IsConnected = false; InvokeDisconnect(); Logging.Logger?.LogWarning("[{Function}]:[{PrinterName}] Network printer connection terminated. Attempting to reconnect. Connection String: {ConnectionString}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, _settings.ConnectionString); Connect(); } - private void AttemptReconnectInfinitely() + private async ValueTask AttemptReconnectInfinitely() { try { @@ -63,11 +69,29 @@ private void AttemptReconnectInfinitely() catch { //Logging.Logger?.LogWarning("[{Function}]:[{PrinterName}] Network printer unable to connect after 5 minutes. Attempting to reconnect. Settings: {Settings}", $"{this}.{MethodBase.GetCurrentMethod().Name}", PrinterName, JsonSerializer.Serialize(_settings)); - Task.Run(async () => { await Task.Delay(250); Connect(); }); + await Task.Delay(250); + CreateTcpConnection(); + await AttemptReconnectInfinitely(); } } private void Connect() + { + CreateTcpConnection(); + try + { + _tcpConnection.Connect(); + } + catch (SocketException) + { + if (!IsConnected) + { + Task.Run(async () => { await AttemptReconnectInfinitely(); }); + } + } + } + + private void CreateTcpConnection() { if (_tcpConnection != null) { @@ -81,16 +105,19 @@ private void Connect() // set events _tcpConnection.Connected += ConnectedEvent; _tcpConnection.Disconnected += DisconnectedEvent; - - Reader = new BinaryReader(_tcpConnection.ReadStream); - Writer = new BinaryWriter(_tcpConnection.WriteStream); - - Task.Run(() => { AttemptReconnectInfinitely(); }); } protected override void OverridableDispose() { + // Dispose to close tcp connection when the printer object is disposed otherwise + // the tcp connection is held until garbage collected + _tcpConnection?.Dispose(); _tcpConnection = null; } + + ~NetworkPrinter() + { + Dispose(true); + } } } diff --git a/ESCPOS_NET/Printers/SerialPrinter.cs b/ESCPOS_NET/Printers/SerialPrinter.cs index a9a6041..2c3e2e9 100644 --- a/ESCPOS_NET/Printers/SerialPrinter.cs +++ b/ESCPOS_NET/Printers/SerialPrinter.cs @@ -8,6 +8,11 @@ public class SerialPrinter : BasePrinter { private readonly SerialPort _serialPort; + /// + /// Expose to be reused elsewhere as Serial Port will be held open. + /// + public SerialPort SerialPort => _serialPort; + public SerialPrinter(string portName, int baudRate) : base() { diff --git a/ESCPOS_NET/Utils/TCPConnection.cs b/ESCPOS_NET/Utils/TCPConnection.cs index 559dbd6..f69c98f 100644 --- a/ESCPOS_NET/Utils/TCPConnection.cs +++ b/ESCPOS_NET/Utils/TCPConnection.cs @@ -5,7 +5,7 @@ namespace ESCPOS_NET { - public class TCPConnection + public class TCPConnection : IDisposable { public Stream ReadStream { get; private set; } = new EchoStream(); public Stream WriteStream { get; private set; } @@ -36,12 +36,30 @@ private void DataReceivedEventHandler(object sender, DataReceivedEventArgs e) { ReadStream.Write(e.Data, 0, e.Data.Length); } + /// + /// Establish a connection to the server without retry. SocketException will be thrown after the certain period of attempts. + /// + /// + public void Connect() + { + _client.Connect(); + } public void ConnectWithRetries(int timeoutMs) { _client.ConnectWithRetries(timeoutMs); } + public void Dispose() + { + Dispose(true); + } + ~TCPConnection() + { + Dispose(false); + } + + private void Dispose(bool disposing) { try { @@ -49,9 +67,17 @@ public void ConnectWithRetries(int timeoutMs) _client.Events.Connected -= ConnectedEventHandler; _client.Events.Disconnected -= DisconnectedEventHandler; _client?.Dispose(); + _client = null; + } + catch { } + try + { + WriteStream?.Dispose(); + ReadStream?.Dispose(); + WriteStream = null; + ReadStream = null; } catch { } } - } }