From 8e48c7b72234527af58bccada4ebe1e876b88bae Mon Sep 17 00:00:00 2001 From: brett Date: Thu, 27 Jan 2011 18:42:09 -0800 Subject: [PATCH 01/49] ninja fix (hostname/fullname got mixed up) --- Floe.UI/ChatControl/ChatControl.xaml.cs | 2 +- Floe.UI/ChatWindow/ChatWindow.xaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Floe.UI/ChatControl/ChatControl.xaml.cs b/Floe.UI/ChatControl/ChatControl.xaml.cs index 7459ce0..d84f1eb 100644 --- a/Floe.UI/ChatControl/ChatControl.xaml.cs +++ b/Floe.UI/ChatControl/ChatControl.xaml.cs @@ -146,7 +146,7 @@ public void Connect(string hostname, int port, string password, bool useSsl, boo !string.IsNullOrEmpty(this.Session.Nickname) ? this.Session.Nickname : App.Settings.Current.User.Nickname, App.Settings.Current.User.Username, - App.Settings.Current.User.Hostname, + App.Settings.Current.User.FullName, App.Settings.Current.User.Invisible, password, autoReconnect); diff --git a/Floe.UI/ChatWindow/ChatWindow.xaml b/Floe.UI/ChatWindow/ChatWindow.xaml index f2474ee..2478ec0 100644 --- a/Floe.UI/ChatWindow/ChatWindow.xaml +++ b/Floe.UI/ChatWindow/ChatWindow.xaml @@ -270,7 +270,7 @@ - + @@ -309,7 +309,7 @@ - + From f35eed2dd86c916feb78820f7eb058eef77f6169 Mon Sep 17 00:00:00 2001 From: Nedry Date: Mon, 7 Feb 2011 14:35:27 -0800 Subject: [PATCH 02/49] fix up merge --- Floe.UI/ChatControl/ChatControl.xaml.cs | 4 ---- Floe.UI/ChatWindow/ChatWindow.xaml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Floe.UI/ChatControl/ChatControl.xaml.cs b/Floe.UI/ChatControl/ChatControl.xaml.cs index c7613b7..1e837a0 100644 --- a/Floe.UI/ChatControl/ChatControl.xaml.cs +++ b/Floe.UI/ChatControl/ChatControl.xaml.cs @@ -145,11 +145,7 @@ public void Connect(string server, int port, bool useSsl, bool autoReconnect, st this.Session.Nickname : App.Settings.Current.User.Nickname, App.Settings.Current.User.Username, App.Settings.Current.User.FullName, -<<<<<<< HEAD - App.Settings.Current.User.Invisible, -======= autoReconnect, ->>>>>>> 1.4 password, App.Settings.Current.User.Invisible, App.Settings.Current.Dcc.FindExternalAddress); diff --git a/Floe.UI/ChatWindow/ChatWindow.xaml b/Floe.UI/ChatWindow/ChatWindow.xaml index ddf9caa..d98a561 100644 --- a/Floe.UI/ChatWindow/ChatWindow.xaml +++ b/Floe.UI/ChatWindow/ChatWindow.xaml @@ -271,9 +271,6 @@ -<<<<<<< HEAD - -======= @@ -293,7 +290,6 @@ ->>>>>>> 1.4 From 014ada65e1c316cfb08ae68abb7083ce71f8ba2f Mon Sep 17 00:00:00 2001 From: brett Date: Sun, 13 Feb 2011 16:58:17 -0800 Subject: [PATCH 03/49] replace callback with SynchronizationContext --- Floe.Net/Dcc/DccOperation.cs | 49 +++++++++-------------- Floe.Net/Dcc/DccXmitReceiver.cs | 1 - Floe.Net/Dcc/DccXmitSender.cs | 1 - Floe.Net/IrcConnection.cs | 14 +++---- Floe.Net/IrcSession.cs | 12 +++--- Floe.UI/ChatWindow/ChatWindow_Commands.cs | 2 +- Floe.UI/ChatWindow/ChatWindow_Events.cs | 4 +- 7 files changed, 34 insertions(+), 49 deletions(-) diff --git a/Floe.Net/Dcc/DccOperation.cs b/Floe.Net/Dcc/DccOperation.cs index 3ddaabe..6d98cbd 100644 --- a/Floe.Net/Dcc/DccOperation.cs +++ b/Floe.Net/Dcc/DccOperation.cs @@ -26,7 +26,7 @@ public abstract class DccOperation : IDisposable private TcpClient _tcpClient; private Thread _socketThread; private ManualResetEvent _endHandle; - private Action _callback; + private SynchronizationContext _syncContext; private long _bytesTransferred; private NetworkStream _stream; @@ -34,8 +34,19 @@ public abstract class DccOperation : IDisposable public EventHandler Disconnected; public EventHandler Error; + /// + /// Gets the remote address. + /// public IPAddress Address { get; private set; } + + /// + /// Gets the remote port. + /// public int Port { get; private set; } + + /// + /// Gets the number of bytes transferred. This is typically only relevant for a file transfer operation. + /// public long BytesTransferred { get @@ -48,9 +59,9 @@ public long BytesTransferred } } - public DccOperation(Action callback = null) + public DccOperation() { - _callback = callback; + _syncContext = SynchronizationContext.Current; _endHandle = new ManualResetEvent(false); } @@ -241,37 +252,13 @@ protected virtual void OnReceived(byte[] buffer, int count) { } - protected void Dispatch(Action handler, T arg) - { - if (_callback != null) - { - _callback(() => handler(arg)); - } - else - { - handler(arg); - } - } - - protected void Dispatch(Action handler) - { - if (_callback != null) - { - _callback(handler); - } - else - { - handler(); - } - } - protected void RaiseEvent(EventHandler evt, T arg) where T : EventArgs { if (evt != null) { - if (_callback != null) + if (_syncContext != null) { - _callback(() => evt(this, arg)); + _syncContext.Post((o) => evt(this, (T)o), arg); } else { @@ -284,9 +271,9 @@ protected void RaiseEvent(EventHandler evt) { if (evt != null) { - if (_callback != null) + if (_syncContext != null) { - _callback(() => evt(this, EventArgs.Empty)); + _syncContext.Post((o) => evt(this, EventArgs.Empty), null); } else { diff --git a/Floe.Net/Dcc/DccXmitReceiver.cs b/Floe.Net/Dcc/DccXmitReceiver.cs index 1261c88..ffc5830 100644 --- a/Floe.Net/Dcc/DccXmitReceiver.cs +++ b/Floe.Net/Dcc/DccXmitReceiver.cs @@ -42,7 +42,6 @@ public sealed class DccXmitReceiver : DccOperation /// A reference to the file to save. If the file exists, a resume will be attempted. If the resume fails, the file will be renamed. /// An optional callback used to route events to another thread. public DccXmitReceiver(FileInfo fileInfo, Action callback = null) - : base(callback) { _fileInfo = fileInfo; this.FileSavedAs = fileInfo.FullName; diff --git a/Floe.Net/Dcc/DccXmitSender.cs b/Floe.Net/Dcc/DccXmitSender.cs index 00835f5..a690208 100644 --- a/Floe.Net/Dcc/DccXmitSender.cs +++ b/Floe.Net/Dcc/DccXmitSender.cs @@ -26,7 +26,6 @@ public sealed class DccXmitSender : DccOperation /// A reference to the file to send. /// An optional callback used to route events to another thread. public DccXmitSender(FileInfo fileInfo, Action callback = null) - : base(callback) { _fileInfo = fileInfo; } diff --git a/Floe.Net/IrcConnection.cs b/Floe.Net/IrcConnection.cs index 702a178..72dc9fa 100644 --- a/Floe.Net/IrcConnection.cs +++ b/Floe.Net/IrcConnection.cs @@ -21,7 +21,7 @@ internal sealed class IrcConnection : IDisposable private ConcurrentQueue _writeQueue; private ManualResetEvent _writeWaitHandle; private ManualResetEvent _endWaitHandle; - private Action _callback; + private SynchronizationContext _syncContext; public event EventHandler Connected; public event EventHandler Disconnected; @@ -32,9 +32,9 @@ internal sealed class IrcConnection : IDisposable public IPAddress ExternalAddress { get { return ((IPEndPoint)_tcpClient.Client.LocalEndPoint).Address; } } - public IrcConnection(Action callback) + public IrcConnection() { - _callback = callback; + _syncContext = SynchronizationContext.Current; } public void Open(string server, int port, bool isSecure) @@ -217,9 +217,9 @@ private void SocketLoop() private void Dispatch(Action action, T arg) { - if (_callback != null) + if (_syncContext != null) { - _callback(() => action(arg)); + _syncContext.Post((o) => action((T)o), arg); } else { @@ -229,9 +229,9 @@ private void Dispatch(Action action, T arg) private void Dispatch(Action action) { - if (_callback != null) + if (_syncContext != null) { - _callback(action); + _syncContext.Post((o) => ((Action)o)(), action); } else { diff --git a/Floe.Net/IrcSession.cs b/Floe.Net/IrcSession.cs index 81cc48f..990fa83 100644 --- a/Floe.Net/IrcSession.cs +++ b/Floe.Net/IrcSession.cs @@ -43,7 +43,7 @@ public sealed class IrcSession : IDisposable private List _captures; private bool _isWaitingForActivity; private bool _findExternalAddress; - private Action _callback; + private SynchronizationContext _syncContext; /// /// Gets the server to which the session is connected or will connect. @@ -228,13 +228,13 @@ private set /// /// An optional callback function that may be used to route events to an appropriate /// thread. The typical usage will be to call Dispatcher.BeginInvoke. - public IrcSession(Action callback = null) + public IrcSession() { this.State = IrcSessionState.Disconnected; this.UserModes = new char[0]; - _callback = callback; + _syncContext = SynchronizationContext.Current; - _conn = new IrcConnection(callback); + _conn = new IrcConnection(); _conn.Connected += new EventHandler(_conn_Connected); _conn.Disconnected += new EventHandler(_conn_Disconnected); _conn.Heartbeat += new EventHandler(_conn_Heartbeat); @@ -773,9 +773,9 @@ private void OnStateChanged() } _reconnectTimer = new Timer(new TimerCallback((obj) => { - if (_callback != null) + if (_syncContext != null) { - _callback(this.OnReconnect); + _syncContext.Post((o) => ((Action)o)(), (Action)this.OnReconnect); } else { diff --git a/Floe.UI/ChatWindow/ChatWindow_Commands.cs b/Floe.UI/ChatWindow/ChatWindow_Commands.cs index 10eb0eb..cf4a3ee 100644 --- a/Floe.UI/ChatWindow/ChatWindow_Commands.cs +++ b/Floe.UI/ChatWindow/ChatWindow_Commands.cs @@ -74,7 +74,7 @@ private void ExecuteCloseTab(object sender, ExecutedRoutedEventArgs e) private void ExecuteNewTab(object sender, ExecutedRoutedEventArgs e) { - this.AddPage(new ChatControl(new IrcSession((a) => this.Dispatcher.BeginInvoke(a)), null), true); + this.AddPage(new ChatControl(new IrcSession(), null), true); } private void ExecuteDetach(object sender, ExecutedRoutedEventArgs e) diff --git a/Floe.UI/ChatWindow/ChatWindow_Events.cs b/Floe.UI/ChatWindow/ChatWindow_Events.cs index 59453ba..d2cafc3 100644 --- a/Floe.UI/ChatWindow/ChatWindow_Events.cs +++ b/Floe.UI/ChatWindow/ChatWindow_Events.cs @@ -118,7 +118,7 @@ private void session_RawMessageReceived(object sender, IrcEventArgs e) private void ChatWindow_Loaded(object sender, RoutedEventArgs e) { - var session = new IrcSession((a) => this.Dispatcher.BeginInvoke(a)); + var session = new IrcSession(); this.AddPage(new ChatControl(session, null), true); if (Application.Current.MainWindow == this) @@ -135,7 +135,7 @@ private void ChatWindow_Loaded(object sender, RoutedEventArgs e) { if (i++ > 0) { - this.AddPage(new ChatControl(new IrcSession((a) => this.Dispatcher.BeginInvoke(a)), null), false); + this.AddPage(new ChatControl(new IrcSession(), null), false); } var page = this.Items[this.Items.Count - 1] as ChatTabItem; if (page != null) From c3a356100f23229c063c0d6ec6de111428afbd17 Mon Sep 17 00:00:00 2001 From: brett Date: Mon, 14 Feb 2011 21:36:44 -0800 Subject: [PATCH 04/49] add standard DCC support --- Floe.Audio/Floe.Audio.csproj | 56 +++++++++ Floe.Audio/Interop/IMMDevice.cs | 13 ++ Floe.Audio/Interop/IMMDeviceEnumerator.cs | 62 ++++++++++ Floe.Audio/Interop/IMMNotificationClient.cs | 13 ++ Floe.Audio/Properties/AssemblyInfo.cs | 36 ++++++ Floe.Configuration/Properties/AssemblyInfo.cs | 1 - Floe.Net/Dcc/DccOperation.cs | 94 +++++++++----- Floe.Net/Dcc/DccSendReceiver.cs | 83 +++++++++++++ Floe.Net/Dcc/DccSendSender.cs | 77 ++++++++++++ Floe.Net/Dcc/DccXmitReceiver.cs | 32 +++-- Floe.Net/Dcc/DccXmitSender.cs | 68 +++++----- Floe.Net/Dcc/NatHelper.cs | 40 +++--- Floe.Net/Floe.Net.csproj | 2 + Floe.Net/IrcConnection.cs | 56 +++++---- Floe.Net/IrcSession.cs | 2 - Floe.UI/Application/App.xaml.cs | 19 +-- Floe.UI/Application/App_Resources.cs | 10 +- Floe.UI/ChannelWindow/ChannelWindow.xaml.cs | 1 - .../ChannelWindow/ChannelWindow_WndProc.cs | 3 +- Floe.UI/ChatBox/ChatPresenter_Searching.cs | 1 - Floe.UI/ChatBox/Enums.cs | 3 - Floe.UI/ChatControl/ChatControl.xaml | 2 + Floe.UI/ChatControl/ChatControl.xaml.cs | 1 - Floe.UI/ChatControl/ChatControl_Commands.cs | 19 ++- Floe.UI/ChatControl/ChatControl_NickList.cs | 1 - Floe.UI/ChatControl/ChatControl_Slap.cs | 5 - Floe.UI/ChatWindow/ChatPage.cs | 5 +- Floe.UI/ChatWindow/ChatTabItem.cs | 4 - Floe.UI/ChatWindow/ChatWindow.xaml | 1 + Floe.UI/ChatWindow/ChatWindow.xaml.cs | 2 - Floe.UI/ChatWindow/ChatWindow_Commands.cs | 22 ++-- Floe.UI/ChatWindow/ChatWindow_Dcc.cs | 117 +++++++++++++----- Floe.UI/ChatWindow/ChatWindow_WndProc.cs | 1 - Floe.UI/Converters.cs | 3 +- Floe.UI/Dialogs/ConfirmDialog.xaml.cs | 10 -- Floe.UI/FileControl/FileControl.xaml | 8 +- Floe.UI/FileControl/FileControl.xaml.cs | 62 ++++++++-- Floe.UI/HsvColor.cs | 3 - Floe.UI/Interop/FolderBrowser.cs | 2 - Floe.UI/Properties/AssemblyInfo.cs | 2 - Floe.UI/Resources/Help.txt | 3 +- Floe.UI/Settings/DccSettingsControl.xaml.cs | 10 -- Floe.sln | 14 ++- 43 files changed, 711 insertions(+), 258 deletions(-) create mode 100644 Floe.Audio/Floe.Audio.csproj create mode 100644 Floe.Audio/Interop/IMMDevice.cs create mode 100644 Floe.Audio/Interop/IMMDeviceEnumerator.cs create mode 100644 Floe.Audio/Interop/IMMNotificationClient.cs create mode 100644 Floe.Audio/Properties/AssemblyInfo.cs create mode 100644 Floe.Net/Dcc/DccSendReceiver.cs create mode 100644 Floe.Net/Dcc/DccSendSender.cs diff --git a/Floe.Audio/Floe.Audio.csproj b/Floe.Audio/Floe.Audio.csproj new file mode 100644 index 0000000..9fa022c --- /dev/null +++ b/Floe.Audio/Floe.Audio.csproj @@ -0,0 +1,56 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {B3C6437F-718B-440A-96D9-7E0627249948} + Library + Properties + Floe.Audio + Floe.Audio + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Floe.Audio/Interop/IMMDevice.cs b/Floe.Audio/Interop/IMMDevice.cs new file mode 100644 index 0000000..05853e0 --- /dev/null +++ b/Floe.Audio/Interop/IMMDevice.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Floe.Audio.Interop +{ + [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMMDevice + { + } +} diff --git a/Floe.Audio/Interop/IMMDeviceEnumerator.cs b/Floe.Audio/Interop/IMMDeviceEnumerator.cs new file mode 100644 index 0000000..0383724 --- /dev/null +++ b/Floe.Audio/Interop/IMMDeviceEnumerator.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Floe.Audio.Interop +{ + enum DataFlow + { + Reader = 0, + Capture = 1, + All = 2 + } + + [Flags] + enum DeviceState + { + Active = 0x01, + Disabled = 0x02, + NotPresent = 0x04, + Unplugged = 0x08, + All = 0x0f + } + + enum Role + { + Console = 0, + Multimedia = 1, + Communications = 2 + } + + [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMMDeviceCollection + { + void GetCount(out int numDevices); + void Item(int deviceNum, out IMMDevice device); + } + + [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMMDeviceEnumerator + { + void EnumAudioEndpoints(DataFlow dataFlow, DeviceState deviceStates, out IMMDeviceCollection devices); + void GetDefaultAudioEndpoint(DataFlow dataFlow, Role role, out IMMDevice device); + void GetDevice(string deviceId, out IMMDevice device); + void RegisterEndpointNotificationCallback(IMMNotificationClient client); + void UnregisterEndpointNotificationCallback(IMMNotificationClient client); + } + + static class MMDeviceEnumerator + { + [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] + private class CoClass + { + } + + public static IMMDeviceEnumerator Create() + { + return new CoClass() as IMMDeviceEnumerator; + } + } +} diff --git a/Floe.Audio/Interop/IMMNotificationClient.cs b/Floe.Audio/Interop/IMMNotificationClient.cs new file mode 100644 index 0000000..3aa472d --- /dev/null +++ b/Floe.Audio/Interop/IMMNotificationClient.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace Floe.Audio.Interop +{ + [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IMMNotificationClient + { + } +} diff --git a/Floe.Audio/Properties/AssemblyInfo.cs b/Floe.Audio/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..06c3243 --- /dev/null +++ b/Floe.Audio/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("Floe.Audio")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Floe.Audio")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[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("aad531c2-0496-4bb4-a5b0-0b9aeb8f4c8a")] + +// 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/Floe.Configuration/Properties/AssemblyInfo.cs b/Floe.Configuration/Properties/AssemblyInfo.cs index 86df790..3965cc9 100644 --- a/Floe.Configuration/Properties/AssemblyInfo.cs +++ b/Floe.Configuration/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("Floe.Configuration.dll")] diff --git a/Floe.Net/Dcc/DccOperation.cs b/Floe.Net/Dcc/DccOperation.cs index 6d98cbd..82adbd3 100644 --- a/Floe.Net/Dcc/DccOperation.cs +++ b/Floe.Net/Dcc/DccOperation.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Threading; @@ -19,13 +17,15 @@ public abstract class DccOperation : IDisposable { private const int ConnectTimeout = 60 * 1000; private const int ListenTimeout = 5 * 60 * 1000; - private const int BufferSize = 2048; + private const int BufferSize = 4096; private const int MinPort = 1024; private TcpListener _listener; private TcpClient _tcpClient; private Thread _socketThread; private ManualResetEvent _endHandle; + private ConcurrentQueue> _writeQueue; + private ManualResetEvent _writeHandle; private SynchronizationContext _syncContext; private long _bytesTransferred; private NetworkStream _stream; @@ -59,10 +59,12 @@ public long BytesTransferred } } - public DccOperation() + protected DccOperation() { _syncContext = SynchronizationContext.Current; _endHandle = new ManualResetEvent(false); + _writeHandle = new ManualResetEvent(false); + _writeQueue = new ConcurrentQueue>(); } /// @@ -198,38 +200,31 @@ public void Connect(IPAddress address, int port) _socketThread.Start(); } - public bool Write(byte[] data, int offset, int size) + /// + /// Closes an active DCC session or cancels the listener. + /// + public void Dispose() { - var ar = _stream.BeginWrite(data, offset, size, null, null); - int index = WaitHandle.WaitAny(new[] { ar.AsyncWaitHandle, _endHandle }); - switch (index) + this.Close(); + if (_tcpClient != null) { - case 0: - _stream.EndWrite(ar); - return true; - default: - return false; + _tcpClient.Close(); + } + if (_listener != null) + { + _listener.Stop(); } } - public void Close() + protected void QueueWrite(byte[] data, int offset, int size) { - _endHandle.Set(); + _writeQueue.Enqueue(new Tuple(data, offset, size)); + _writeHandle.Set(); } - /// - /// Closes an active DCC session or cancels the listener. - /// - public void Dispose() + protected void Close() { - if (_tcpClient != null) - { - try - { - _tcpClient.Close(); - } - catch { } - } + _endHandle.Set(); } protected virtual void OnConnected() @@ -252,6 +247,10 @@ protected virtual void OnReceived(byte[] buffer, int count) { } + protected virtual void OnSent(byte[] buffer, int offset, int count) + { + } + protected void RaiseEvent(EventHandler evt, T arg) where T : EventArgs { if (evt != null) @@ -285,17 +284,37 @@ protected void RaiseEvent(EventHandler evt) private void SocketLoop() { var readBuffer = new byte[BufferSize]; + Tuple outgoing = null; + IAsyncResult arr = null, arw = null; try { while (_tcpClient.Connected) { - var ar = _stream.BeginRead(readBuffer, 0, BufferSize, null, null); - int idx = WaitHandle.WaitAny(new[] { ar.AsyncWaitHandle, _endHandle }); + if (arr == null) + { + arr = _stream.BeginRead(readBuffer, 0, BufferSize, null, null); + } + _writeHandle.Reset(); + if (arw == null && _writeQueue.TryDequeue(out outgoing)) + { + if (outgoing.Item1 == null) + { + break; + } + arw = _stream.BeginWrite(outgoing.Item1, outgoing.Item2, outgoing.Item3, null, null); + } + int idx = WaitHandle.WaitAny( + new[] { + arr.AsyncWaitHandle, + arw != null ? arw.AsyncWaitHandle : _writeHandle, + _endHandle + }); switch (idx) { case 0: - int count = _stream.EndRead(ar); + int count = _stream.EndRead(arr); + arr = null; if (count <= 0) { return; @@ -303,8 +322,19 @@ private void SocketLoop() this.OnReceived(readBuffer, count); break; case 1: + if (arw != null) + { + _stream.EndWrite(arw); + arw = null; + this.OnSent(outgoing.Item1, outgoing.Item2, outgoing.Item3); + } + break; + case 2: + if (arw != null) + { + _stream.EndWrite(arw); + } return; - } } } diff --git a/Floe.Net/Dcc/DccSendReceiver.cs b/Floe.Net/Dcc/DccSendReceiver.cs new file mode 100644 index 0000000..7312f57 --- /dev/null +++ b/Floe.Net/Dcc/DccSendReceiver.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Net; +using System.Linq; + +namespace Floe.Net +{ + /// + /// A class responsible for receiving files from another source using the DCC XMIT protocol. + /// + public sealed class DccSendReceiver : DccOperation + { + private FileInfo _fileInfo; + private FileStream _fileStream; + + /// + /// Gets or sets a value indicating whether the specified file will be overwritten if it already exists and a resume is not possible. If this is set to false, + /// the file will be renamed. + /// + public bool ForceOverwrite { get; set; } + + /// + /// Gets the path to the file that was created. This may differ from the initial file path if it has been automatically renamed to avoid overwriting a file. + /// + public string FileSavedAs { get; private set; } + + /// + /// Construct a new DccSendReceiver. + /// + /// A reference to the file to save. If the file exists, a resume will be attempted. If the resume fails, the file will be renamed. + public DccSendReceiver(FileInfo fileInfo) + { + _fileInfo = fileInfo; + this.FileSavedAs = fileInfo.FullName; + } + + protected override void OnConnected() + { + base.OnConnected(); + + int i = 1; + string fileName = Path.GetFileNameWithoutExtension(_fileInfo.Name); + while (_fileInfo.Exists && !this.ForceOverwrite) + { + _fileInfo = new FileInfo( + Path.Combine(_fileInfo.DirectoryName, + string.Format("{0} ({1}).{2}", fileName, i++, _fileInfo.Extension))); + } + this.FileSavedAs = _fileInfo.FullName; + _fileStream = new FileStream(_fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.Read); + } + + protected override void OnReceived(byte[] buffer, int count) + { + base.OnReceived(buffer, count); + + _fileStream.Write(buffer, 0, count); + this.BytesTransferred += count; + byte[] writeBuffer = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)this.BytesTransferred)); + this.QueueWrite(writeBuffer, 0, 4); + } + + protected override void OnDisconnected() + { + base.OnDisconnected(); + this.CloseFile(); + } + + protected override void OnError(Exception ex) + { + base.OnError(ex); + this.CloseFile(); + } + + private void CloseFile() + { + if (_fileStream != null) + { + _fileStream.Dispose(); + } + } + } +} diff --git a/Floe.Net/Dcc/DccSendSender.cs b/Floe.Net/Dcc/DccSendSender.cs new file mode 100644 index 0000000..3a8afd2 --- /dev/null +++ b/Floe.Net/Dcc/DccSendSender.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Net; +using System.Linq; + +namespace Floe.Net +{ + /// + /// This class is responsible for sending files via the DCC SEND protocol. Unlike most implementations, + /// this class does not care about receiving acknowledgements for each chunk of file, because that is + /// worthless. Resume is not supported. + /// + public sealed class DccSendSender : DccOperation + { + private const int SendChunkSize = 4096; + + private FileInfo _fileInfo; + private FileStream _fileStream; + private byte[] _buffer = new byte[SendChunkSize]; + + /// + /// Construct a new DccSendSender. + /// + /// A reference to the file to send. + public DccSendSender(FileInfo fileInfo) + { + _fileInfo = fileInfo; + } + + protected override void OnConnected() + { + base.OnConnected(); + + _fileStream = new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.Read); + this.WriteDataBlock(); + } + + protected override void OnSent(byte[] buffer, int offset, int count) + { + this.BytesTransferred += count; + this.WriteDataBlock(); + } + + protected override void OnDisconnected() + { + base.OnDisconnected(); + this.CloseFile(); + } + + protected override void OnError(Exception ex) + { + base.OnError(ex); + this.CloseFile(); + } + + private void CloseFile() + { + if (_fileStream != null) + { + _fileStream.Dispose(); + } + } + + private void WriteDataBlock() + { + if (_fileStream.Position < _fileStream.Length) + { + int count = _fileStream.Read(_buffer, 0, SendChunkSize); + this.QueueWrite(_buffer, 0, count); + } + else + { + this.Close(); + } + } + } +} diff --git a/Floe.Net/Dcc/DccXmitReceiver.cs b/Floe.Net/Dcc/DccXmitReceiver.cs index ffc5830..8af86bf 100644 --- a/Floe.Net/Dcc/DccXmitReceiver.cs +++ b/Floe.Net/Dcc/DccXmitReceiver.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; using System.Net; @@ -40,8 +37,7 @@ public sealed class DccXmitReceiver : DccOperation /// Construct a new DccXmitReceiver. /// /// A reference to the file to save. If the file exists, a resume will be attempted. If the resume fails, the file will be renamed. - /// An optional callback used to route events to another thread. - public DccXmitReceiver(FileInfo fileInfo, Action callback = null) + public DccXmitReceiver(FileInfo fileInfo) { _fileInfo = fileInfo; this.FileSavedAs = fileInfo.FullName; @@ -80,7 +76,7 @@ protected override void OnReceived(byte[] buffer, int count) this.FileSavedAs = _fileInfo.FullName; _fileStream = new FileStream(_fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.Read); } - this.Write(resumeBytes, 0, 4); + this.QueueWrite(resumeBytes, 0, 4); _isTransferring = true; } } @@ -91,18 +87,6 @@ protected override void OnReceived(byte[] buffer, int count) } } - private void CloseFile() - { - if (_fileStream != null) - { - _fileStream.Dispose(); - if (_timeStamp > 0 && File.Exists(_fileInfo.FullName)) - { - File.SetLastWriteTimeUtc(_fileInfo.FullName, new DateTime(1970, 1, 1) + TimeSpan.FromSeconds(_timeStamp)); - } - } - } - protected override void OnDisconnected() { base.OnDisconnected(); @@ -114,5 +98,17 @@ protected override void OnError(Exception ex) base.OnError(ex); this.CloseFile(); } + + private void CloseFile() + { + if (_fileStream != null) + { + _fileStream.Dispose(); + if (_timeStamp > 0 && File.Exists(_fileInfo.FullName)) + { + File.SetLastWriteTimeUtc(_fileInfo.FullName, new DateTime(1970, 1, 1) + TimeSpan.FromSeconds(_timeStamp)); + } + } + } } } diff --git a/Floe.Net/Dcc/DccXmitSender.cs b/Floe.Net/Dcc/DccXmitSender.cs index a690208..d8553fd 100644 --- a/Floe.Net/Dcc/DccXmitSender.cs +++ b/Floe.Net/Dcc/DccXmitSender.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; using System.Net; @@ -12,20 +9,20 @@ namespace Floe.Net /// public sealed class DccXmitSender : DccOperation { - private const int SendChunkSize = 2048; + private const int SendChunkSize = 4096; private bool _isTransferring = false; private FileInfo _fileInfo; private FileStream _fileStream; - private byte[] _resumeBytes = new Byte[4]; + private byte[] _resumeBytes = new byte[4]; + private byte[] _buffer = new byte[SendChunkSize]; private int _handshakeBytesReceived; /// /// Construct a new DccXmitSender. /// /// A reference to the file to send. - /// An optional callback used to route events to another thread. - public DccXmitSender(FileInfo fileInfo, Action callback = null) + public DccXmitSender(FileInfo fileInfo) { _fileInfo = fileInfo; } @@ -35,7 +32,7 @@ protected override void OnConnected() base.OnConnected(); var timeStampBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(_fileInfo.LastWriteTimeUtc - new DateTime(1970, 1, 1)).TotalSeconds)); - this.Write(timeStampBytes, 0, 4); + this.QueueWrite(timeStampBytes, 0, 4); } protected override void OnReceived(byte[] buffer, int count) @@ -57,50 +54,51 @@ protected override void OnReceived(byte[] buffer, int count) this.BytesTransferred = resume; } _isTransferring = true; - - buffer = new byte[SendChunkSize]; - try - { - while (_fileStream.Position < _fileStream.Length) - { - count = _fileStream.Read(buffer, 0, SendChunkSize); - this.BytesTransferred += count; - if (!this.Write(buffer, 0, count)) - { - return; - } - } - } - catch (IOException ex) - { - this.OnError(ex); - } - finally - { - this.Close(); - } + this.WriteDataBlock(); } } } - protected override void OnDisconnected() + protected override void OnSent(byte[] buffer, int offset, int count) { - base.OnDisconnected(); - - if (_fileStream != null) + if (_isTransferring) { - _fileStream.Dispose(); + this.BytesTransferred += count; + this.WriteDataBlock(); } } + protected override void OnDisconnected() + { + base.OnDisconnected(); + this.CloseFile(); + } + protected override void OnError(Exception ex) { base.OnError(ex); + this.CloseFile(); + } + private void CloseFile() + { if (_fileStream != null) { _fileStream.Dispose(); } } + + private void WriteDataBlock() + { + if (_fileStream.Position < _fileStream.Length) + { + int count = _fileStream.Read(_buffer, 0, SendChunkSize); + this.QueueWrite(_buffer, 0, count); + } + else + { + this.Close(); + } + } } } diff --git a/Floe.Net/Dcc/NatHelper.cs b/Floe.Net/Dcc/NatHelper.cs index 207cf14..007e3de 100644 --- a/Floe.Net/Dcc/NatHelper.cs +++ b/Floe.Net/Dcc/NatHelper.cs @@ -1,14 +1,13 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; using System.Net; using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; -using System.Threading; namespace Floe.Net { @@ -76,22 +75,29 @@ public static IAsyncResult BeginDiscover(AsyncCallback callback = null, object s { sock.SendTo(outBuf, endpoint); sock.ReceiveTimeout = DiscoverTimeout; - int length = sock.Receive(inBuf); - string data = Encoding.ASCII.GetString(inBuf, 0, length).ToLowerInvariant(); - - var match = ResponseLocation.Match(data); - if (match.Success && match.Groups["location"].Success) + while (true) { - System.Diagnostics.Debug.WriteLine("Found UPnP device at " + match.Groups["location"]); - string controlUrl = GetServiceUrl(match.Groups["location"].Value); - if (!string.IsNullOrEmpty(controlUrl)) + int length = sock.Receive(inBuf); + string data = Encoding.ASCII.GetString(inBuf, 0, length).ToLowerInvariant(); + + var match = ResponseLocation.Match(data); + if (match.Success && match.Groups["location"].Success) { - _controlUrl = controlUrl; - System.Diagnostics.Debug.WriteLine("Found control URL at " + _controlUrl); - ar.IsSuccessful = true; + System.Diagnostics.Debug.WriteLine("Found UPnP device at " + match.Groups["location"]); + string controlUrl = GetServiceUrl(match.Groups["location"].Value); + if (!string.IsNullOrEmpty(controlUrl)) + { + _controlUrl = controlUrl; + System.Diagnostics.Debug.WriteLine("Found control URL at " + _controlUrl); + ar.IsSuccessful = true; + break; + } + else + { + continue; + } } } - break; } catch (Exception ex) { @@ -305,7 +311,7 @@ private static string GetServiceUrl(string url) } if (node.Value.IndexOf(DeviceType, StringComparison.OrdinalIgnoreCase) < 0) { - throw new ApplicationException("Response from incorrect device type: " + node.Value); + return null; } node = doc.Root.XPathSelectElement(ControlUrlPath, ns); if (node == null) diff --git a/Floe.Net/Floe.Net.csproj b/Floe.Net/Floe.Net.csproj index 78ad2ba..aced7fc 100644 --- a/Floe.Net/Floe.Net.csproj +++ b/Floe.Net/Floe.Net.csproj @@ -48,6 +48,8 @@ + + diff --git a/Floe.Net/IrcConnection.cs b/Floe.Net/IrcConnection.cs index 72dc9fa..737b4e2 100644 --- a/Floe.Net/IrcConnection.cs +++ b/Floe.Net/IrcConnection.cs @@ -143,27 +143,40 @@ private void SocketLoop() this.Dispatch(this.OnConnected); byte[] readBuffer = new byte[512], writeBuffer = new byte[Encoding.UTF8.GetMaxByteCount(512)]; - int count = 0, handleIdx = 0; + int count = 0; var input = new StringBuilder(); - IrcMessage message; + IrcMessage outgoing = null; char last = '\u0000'; + IAsyncResult arr = null, arw = null; while (_tcpClient.Connected) { - if (handleIdx == 0) + if (arr == null) { - ar = stream.BeginRead(readBuffer, 0, 512, null, null); + arr = stream.BeginRead(readBuffer, 0, 512, null, null); } - handleIdx = WaitHandle.WaitAny(new[] { ar.AsyncWaitHandle, _writeWaitHandle, _endWaitHandle }, HeartbeatInterval); - if (!_tcpClient.Connected) + _writeWaitHandle.Reset(); + if (arw == null && _writeQueue.TryDequeue(out outgoing)) { - break; + string output = outgoing.ToString(); + count = Encoding.UTF8.GetBytes(output, 0, output.Length, writeBuffer, 0); + count = Math.Min(510, count); + writeBuffer[count] = 0xd; + writeBuffer[count + 1] = 0xa; + arw = stream.BeginWrite(writeBuffer, 0, count + 2, null, null); } - - switch (handleIdx) + int idx = WaitHandle.WaitAny( + new[] { + arr.AsyncWaitHandle, + arw != null ? arw.AsyncWaitHandle : _writeWaitHandle, + _endWaitHandle }, + HeartbeatInterval); + + switch (idx) { case 0: - count = stream.EndRead(ar); + count = stream.EndRead(arr); + arr = null; if (count == 0) { _tcpClient.Close(); @@ -176,8 +189,8 @@ private void SocketLoop() { if (input.Length > 0) { - message = IrcMessage.Parse(input.ToString()); - this.Dispatch(this.OnMessageReceived, message); + var incoming = IrcMessage.Parse(input.ToString()); + this.Dispatch(this.OnMessageReceived, incoming); input.Clear(); } } @@ -190,21 +203,18 @@ private void SocketLoop() } break; case 1: - _writeWaitHandle.Reset(); - while (_writeQueue.TryDequeue(out message)) + if (arw != null) { - string output = message.ToString(); - count = Encoding.UTF8.GetBytes(output, 0, output.Length, writeBuffer, 0); - count = Math.Min(510, count); - writeBuffer[count] = 0xd; - writeBuffer[count + 1] = 0xa; - - stream.Write(writeBuffer, 0, count + 2); - - this.Dispatch(this.OnMessageSent, message); + stream.EndWrite(arw); + arw = null; + this.Dispatch(this.OnMessageSent, outgoing); } break; case 2: + if (arw != null) + { + stream.EndWrite(arw); + } return; case WaitHandle.WaitTimeout: this.Dispatch(this.OnHeartbeat); diff --git a/Floe.Net/IrcSession.cs b/Floe.Net/IrcSession.cs index 990fa83..91f08f5 100644 --- a/Floe.Net/IrcSession.cs +++ b/Floe.Net/IrcSession.cs @@ -226,8 +226,6 @@ private set /// /// Default constructor. /// - /// An optional callback function that may be used to route events to an appropriate - /// thread. The typical usage will be to call Dispatcher.BeginInvoke. public IrcSession() { this.State = IrcSessionState.Disconnected; diff --git a/Floe.UI/Application/App.xaml.cs b/Floe.UI/Application/App.xaml.cs index 7640399..e0e75c5 100644 --- a/Floe.UI/Application/App.xaml.cs +++ b/Floe.UI/Application/App.xaml.cs @@ -1,14 +1,6 @@ using System; -using System.Windows; -using System.Reflection; using System.Linq; -using System.Windows.Input; -using System.Windows.Threading; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Drawing; -using System.IO; -using Floe.Configuration; +using System.Windows; using Floe.Net; namespace Floe.UI @@ -90,6 +82,15 @@ public static void Alert(Window window, string text) } } + public static string OpenFileDialog(Window owner, string initialDirectory) + { + var dialog = new Microsoft.Win32.OpenFileDialog(); + dialog.CheckFileExists = true; + dialog.Multiselect = false; + dialog.InitialDirectory = initialDirectory; + return dialog.ShowDialog(owner) == true ? dialog.FileName : null; + } + public static void BrowseTo(string url) { try diff --git a/Floe.UI/Application/App_Resources.cs b/Floe.UI/Application/App_Resources.cs index 6c93211..e08f423 100644 --- a/Floe.UI/Application/App_Resources.cs +++ b/Floe.UI/Application/App_Resources.cs @@ -1,15 +1,11 @@ using System; -using System.Windows; -using System.Reflection; +using System.Drawing; using System.Linq; -using System.Windows.Input; -using System.Windows.Threading; +using System.Reflection; +using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; -using System.Drawing; -using System.IO; using Floe.Configuration; -using Floe.Net; namespace Floe.UI { diff --git a/Floe.UI/ChannelWindow/ChannelWindow.xaml.cs b/Floe.UI/ChannelWindow/ChannelWindow.xaml.cs index 8e0b2d6..55b1a35 100644 --- a/Floe.UI/ChannelWindow/ChannelWindow.xaml.cs +++ b/Floe.UI/ChannelWindow/ChannelWindow.xaml.cs @@ -2,7 +2,6 @@ using System.ComponentModel; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; using Floe.Net; diff --git a/Floe.UI/ChannelWindow/ChannelWindow_WndProc.cs b/Floe.UI/ChannelWindow/ChannelWindow_WndProc.cs index 2b2b55d..205eca8 100644 --- a/Floe.UI/ChannelWindow/ChannelWindow_WndProc.cs +++ b/Floe.UI/ChannelWindow/ChannelWindow_WndProc.cs @@ -1,8 +1,7 @@ using System; using System.Windows; -using System.Windows.Media; using System.Windows.Interop; - +using System.Windows.Media; using Floe.UI.Interop; namespace Floe.UI diff --git a/Floe.UI/ChatBox/ChatPresenter_Searching.cs b/Floe.UI/ChatBox/ChatPresenter_Searching.cs index 996cbb9..a6b18ef 100644 --- a/Floe.UI/ChatBox/ChatPresenter_Searching.cs +++ b/Floe.UI/ChatBox/ChatPresenter_Searching.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text.RegularExpressions; using System.Windows; -using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Media.TextFormatting; diff --git a/Floe.UI/ChatBox/Enums.cs b/Floe.UI/ChatBox/Enums.cs index 29707a5..1dcd7b1 100644 --- a/Floe.UI/ChatBox/Enums.cs +++ b/Floe.UI/ChatBox/Enums.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Floe.UI { diff --git a/Floe.UI/ChatControl/ChatControl.xaml b/Floe.UI/ChatControl/ChatControl.xaml index a7fa4c3..ceaacd0 100644 --- a/Floe.UI/ChatControl/ChatControl.xaml +++ b/Floe.UI/ChatControl/ChatControl.xaml @@ -44,6 +44,7 @@ + @@ -60,6 +61,7 @@ + diff --git a/Floe.UI/ChatControl/ChatControl.xaml.cs b/Floe.UI/ChatControl/ChatControl.xaml.cs index 1e837a0..dafa0a7 100644 --- a/Floe.UI/ChatControl/ChatControl.xaml.cs +++ b/Floe.UI/ChatControl/ChatControl.xaml.cs @@ -5,7 +5,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; -using System.Windows.Media; using Floe.Net; namespace Floe.UI diff --git a/Floe.UI/ChatControl/ChatControl_Commands.cs b/Floe.UI/ChatControl/ChatControl_Commands.cs index dd77c6c..a8dae8f 100644 --- a/Floe.UI/ChatControl/ChatControl_Commands.cs +++ b/Floe.UI/ChatControl/ChatControl_Commands.cs @@ -4,7 +4,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Windows; -using System.Windows.Controls; using System.Windows.Input; using Floe.Net; @@ -574,9 +573,10 @@ private void Execute(string command, string arguments) case "DCC": { args = Split(command, arguments, 3, 3); - if (args[0].ToUpperInvariant() == "XMIT") + string dccCmd = args[0].ToUpperInvariant(); + string path = null; + if (dccCmd == "XMIT" || dccCmd == "SEND") { - string path = null; if (System.IO.Path.IsPathRooted(args[2]) && System.IO.File.Exists(args[2])) { path = args[2]; @@ -586,11 +586,18 @@ private void Execute(string command, string arguments) this.Write("Error", "Could not find file " + args[2]); return; } - App.ChatWindow.DccSend(this.Session, new IrcTarget(args[1]), new System.IO.FileInfo(path)); } - else + switch (dccCmd) { - this.Write("Error", "Unsupported DCC mode " + args[0]); + case "XMIT": + App.ChatWindow.DccXmit(this.Session, new IrcTarget(args[1]), new System.IO.FileInfo(path)); + break; + case "SEND": + App.ChatWindow.DccSend(this.Session, new IrcTarget(args[1]), new System.IO.FileInfo(path)); + break; + default: + this.Write("Error", "Unsupported DCC mode " + args[0]); + break; } } break; diff --git a/Floe.UI/ChatControl/ChatControl_NickList.cs b/Floe.UI/ChatControl/ChatControl_NickList.cs index da8e0e4..0129800 100644 --- a/Floe.UI/ChatControl/ChatControl_NickList.cs +++ b/Floe.UI/ChatControl/ChatControl_NickList.cs @@ -1,6 +1,5 @@ using System; using System.Collections.ObjectModel; -using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; diff --git a/Floe.UI/ChatControl/ChatControl_Slap.cs b/Floe.UI/ChatControl/ChatControl_Slap.cs index 1971192..369f57b 100644 --- a/Floe.UI/ChatControl/ChatControl_Slap.cs +++ b/Floe.UI/ChatControl/ChatControl_Slap.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; using System.Windows.Input; using Floe.Net; diff --git a/Floe.UI/ChatWindow/ChatPage.cs b/Floe.UI/ChatWindow/ChatPage.cs index ae353f2..3d5c6e0 100644 --- a/Floe.UI/ChatWindow/ChatPage.cs +++ b/Floe.UI/ChatWindow/ChatPage.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using System.Windows.Data; +using System.Windows.Media; using Floe.Net; namespace Floe.UI diff --git a/Floe.UI/ChatWindow/ChatTabItem.cs b/Floe.UI/ChatWindow/ChatTabItem.cs index 1558b9b..4816aab 100644 --- a/Floe.UI/ChatWindow/ChatTabItem.cs +++ b/Floe.UI/ChatWindow/ChatTabItem.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows.Controls; -using Floe.Net; namespace Floe.UI { diff --git a/Floe.UI/ChatWindow/ChatWindow.xaml b/Floe.UI/ChatWindow/ChatWindow.xaml index d98a561..9f5a6f4 100644 --- a/Floe.UI/ChatWindow/ChatWindow.xaml +++ b/Floe.UI/ChatWindow/ChatWindow.xaml @@ -23,6 +23,7 @@ + diff --git a/Floe.UI/ChatWindow/ChatWindow.xaml.cs b/Floe.UI/ChatWindow/ChatWindow.xaml.cs index 3bf9fbd..fc55ba3 100644 --- a/Floe.UI/ChatWindow/ChatWindow.xaml.cs +++ b/Floe.UI/ChatWindow/ChatWindow.xaml.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Input; using Floe.Net; diff --git a/Floe.UI/ChatWindow/ChatWindow_Commands.cs b/Floe.UI/ChatWindow/ChatWindow_Commands.cs index cf4a3ee..5f3faa7 100644 --- a/Floe.UI/ChatWindow/ChatWindow_Commands.cs +++ b/Floe.UI/ChatWindow/ChatWindow_Commands.cs @@ -2,8 +2,8 @@ using System.Linq; using System.Windows; using System.Windows.Input; -using Microsoft.Win32; using Floe.Net; +using Microsoft.Win32; namespace Floe.UI { @@ -19,6 +19,7 @@ public partial class ChatWindow : Window public readonly static RoutedUICommand MinimizeCommand = new RoutedUICommand("Minimize", "Minimize", typeof(ChatWindow)); public readonly static RoutedUICommand MaximizeCommand = new RoutedUICommand("Maximize", "Maximize", typeof(ChatWindow)); public readonly static RoutedUICommand CloseCommand = new RoutedUICommand("Quit", "Close", typeof(ChatWindow)); + public readonly static RoutedUICommand DccXmitCommand = new RoutedUICommand("DCC Xmit...", "DccXmit", typeof(ChatWindow)); public readonly static RoutedUICommand DccSendCommand = new RoutedUICommand("DCC Send...", "DccSend", typeof(ChatWindow)); private void ExecuteChat(object sender, ExecutedRoutedEventArgs e) @@ -154,16 +155,23 @@ private void ExecuteClose(object sender, ExecutedRoutedEventArgs e) this.Close(); } + private void ExecuteDccXmit(object sender, ExecutedRoutedEventArgs e) + { + var control = tabsChat.SelectedContent as ChatPage; + string fileName = App.OpenFileDialog(this, App.Settings.Current.Dcc.DownloadFolder); + if (!string.IsNullOrEmpty(fileName)) + { + this.DccXmit(control.Session, new IrcTarget((string)e.Parameter), new System.IO.FileInfo(fileName)); + } + } + private void ExecuteDccSend(object sender, ExecutedRoutedEventArgs e) { var control = tabsChat.SelectedContent as ChatPage; - var dialog = new OpenFileDialog(); - dialog.CheckFileExists = true; - dialog.Multiselect = false; - dialog.InitialDirectory = App.Settings.Current.Dcc.DownloadFolder; - if (dialog.ShowDialog(this) == true) + string fileName = App.OpenFileDialog(this, App.Settings.Current.Dcc.DownloadFolder); + if (!string.IsNullOrEmpty(fileName)) { - this.DccSend(control.Session, new IrcTarget((string)e.Parameter), new System.IO.FileInfo(dialog.FileName)); + this.DccSend(control.Session, new IrcTarget((string)e.Parameter), new System.IO.FileInfo(fileName)); } } } diff --git a/Floe.UI/ChatWindow/ChatWindow_Dcc.cs b/Floe.UI/ChatWindow/ChatWindow_Dcc.cs index a4d9985..4e9d195 100644 --- a/Floe.UI/ChatWindow/ChatWindow_Dcc.cs +++ b/Floe.UI/ChatWindow/ChatWindow_Dcc.cs @@ -1,27 +1,42 @@ using System; -using System.Linq; -using System.Windows; -using System.Net; using System.IO; - -using Floe.Configuration; +using System.Net; +using System.Windows; using Floe.Net; namespace Floe.UI { public partial class ChatWindow : Window { + public void DccXmit(IrcSession session, IrcTarget target, FileInfo file) + { + var page = new FileControl(session, target, DccMethod.Xmit); + App.Create(session, page, true); + page.StartSend(file, (port) => + { + if (port > 0) + { + session.SendCtcp(target, new CtcpCommand("DCC", "XMIT", "CLEAR", + ConvertIPAddressToString(session.ExternalAddress), + IPAddress.HostToNetworkOrder(BitConverter.ToUInt32(session.ExternalAddress.GetAddressBytes(), 0)).ToString(), + port.ToString(), file.Name, file.Length.ToString()), false); + } + }); + } + public void DccSend(IrcSession session, IrcTarget target, FileInfo file) { - var page = new FileControl(session, target); + var page = new FileControl(session, target, DccMethod.Send); App.Create(session, page, true); page.StartSend(file, (port) => + { + if (port > 0) { - if (port > 0) - { - session.SendCtcp(target, new CtcpCommand("DCC", "XMIT", "CLEAR", session.ExternalAddress.ToString(), port.ToString(), file.Name, file.Length.ToString()), false); - } - }); + session.SendCtcp(target, new CtcpCommand("DCC", "SEND", file.Name, + ConvertIPAddressToString(session.ExternalAddress), + port.ToString(), file.Length.ToString(), "T"), false); + } + }); } private bool HandleDcc(IrcSession session, IrcTarget target, string[] args) @@ -36,33 +51,66 @@ private bool HandleDcc(IrcSession session, IrcTarget target, string[] args) switch (type) { case "XMIT": - if (args.Length < 4) { - return false; - } + if (args.Length < 5) + { + return false; + } - IPAddress addr; - int port; - if (string.Compare(args[1], "CLEAR", StringComparison.OrdinalIgnoreCase) != 0 || - !IPAddress.TryParse(args[2], out addr) || - !int.TryParse(args[3], out port)) - { - session.SendCtcp(target, new CtcpCommand("ERRMSG", "DCC", args[0], args[1], "unavailable"), true); - return true; + IPAddress addr; + int port; + if (string.Compare(args[1], "CLEAR", StringComparison.OrdinalIgnoreCase) != 0 || + !IPAddress.TryParse(args[2], out addr) || + !int.TryParse(args[3], out port)) + { + session.SendCtcp(target, new CtcpCommand("ERRMSG", "DCC", args[0], args[1], "unavailable"), true); + return true; + } + + string name = args[4]; + long size = 0; + if (args.Length > 5) + { + long.TryParse(args[5], out size); + } + + var page = new FileControl(session, target, DccMethod.Xmit); + page.StartReceive(addr, port, name, size); + page.NotifyState = NotifyState.Alert; + App.Create(session, page, false); + App.Alert(Window.GetWindow(page), string.Format("{0} wants to send you a file.", target.Name)); } + break; - string name = args.Length > 4 ? args[4] : null; - long size = 0; - if (args.Length > 5) + case "SEND": { - long.TryParse(args[5], out size); - } + if (args.Length < 4) + { + return false; + } - var page = new FileControl(session, target); - page.StartReceive(addr, port, name, size); - page.NotifyState = NotifyState.Alert; - App.Create(session, page, false); - App.Alert(Window.GetWindow(page), string.Format("{0} wants to send you a file.", target.Name)); + IPAddress addr; + int port; + string name = args[1]; + if (!IPAddress.TryParse(args[2], out addr) || + !int.TryParse(args[3], out port)) + { + session.SendCtcp(target, new CtcpCommand("ERRMSG", "DCC", args[0], "unavailable"), true); + return true; + } + + long size = 0; + if (args.Length > 4) + { + long.TryParse(args[4], out size); + } + + var page = new FileControl(session, target, DccMethod.Send); + page.StartReceive(addr, port, name, size); + page.NotifyState = NotifyState.Alert; + App.Create(session, page, false); + App.Alert(Window.GetWindow(page), string.Format("{0} wants to send you a file.", target.Name)); + } break; default: @@ -71,5 +119,10 @@ private bool HandleDcc(IrcSession session, IrcTarget target, string[] args) } return true; } + + private static string ConvertIPAddressToString(IPAddress address) + { + return IPAddress.HostToNetworkOrder(BitConverter.ToInt32(address.GetAddressBytes(), 0)).ToString(); + } } } diff --git a/Floe.UI/ChatWindow/ChatWindow_WndProc.cs b/Floe.UI/ChatWindow/ChatWindow_WndProc.cs index 315bab6..dd843da 100644 --- a/Floe.UI/ChatWindow/ChatWindow_WndProc.cs +++ b/Floe.UI/ChatWindow/ChatWindow_WndProc.cs @@ -3,7 +3,6 @@ using System.Windows.Controls; using System.Windows.Interop; using System.Windows.Media; -using System.Linq; using Floe.UI.Interop; namespace Floe.UI diff --git a/Floe.UI/Converters.cs b/Floe.UI/Converters.cs index 9a53d40..097f1a4 100644 --- a/Floe.UI/Converters.cs +++ b/Floe.UI/Converters.cs @@ -1,9 +1,8 @@ using System; using System.Globalization; -using System.Linq; +using System.Text; using System.Windows.Data; using System.Windows.Media; -using System.Text; namespace Floe.UI { diff --git a/Floe.UI/Dialogs/ConfirmDialog.xaml.cs b/Floe.UI/Dialogs/ConfirmDialog.xaml.cs index 17f6ffe..caca259 100644 --- a/Floe.UI/Dialogs/ConfirmDialog.xaml.cs +++ b/Floe.UI/Dialogs/ConfirmDialog.xaml.cs @@ -1,15 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; namespace Floe.UI { diff --git a/Floe.UI/FileControl/FileControl.xaml b/Floe.UI/FileControl/FileControl.xaml index 5376104..d514df5 100644 --- a/Floe.UI/FileControl/FileControl.xaml +++ b/Floe.UI/FileControl/FileControl.xaml @@ -131,9 +131,13 @@ - + + + + + - + diff --git a/Floe.UI/FileControl/FileControl.xaml.cs b/Floe.UI/FileControl/FileControl.xaml.cs index de3c432..62a3774 100644 --- a/Floe.UI/FileControl/FileControl.xaml.cs +++ b/Floe.UI/FileControl/FileControl.xaml.cs @@ -1,12 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; -using System.Windows; -using System.Collections.Generic; using System.Threading; -using System.Linq; -using Microsoft.Win32; +using System.Windows; using Floe.Net; +using Microsoft.Win32; namespace Floe.UI { @@ -19,6 +19,12 @@ public enum FileStatus Sent } + public enum DccMethod + { + Send, + Xmit + } + public partial class FileControl : ChatPage { private const int PollTime = 250; @@ -96,9 +102,18 @@ public bool IsDangerous set { this.SetValue(IsDangerousProperty, value); } } - public FileControl(IrcSession session, IrcTarget target) + public static readonly DependencyProperty DccMethodProperty = + DependencyProperty.Register("DccMethod", typeof(DccMethod), typeof(FileControl)); + public DccMethod DccMethod + { + get { return (DccMethod)this.GetValue(DccMethodProperty); } + set { this.SetValue(DccMethodProperty, value); } + } + + public FileControl(IrcSession session, IrcTarget target, DccMethod method) : base(ChatPageType.DccFile, session, target, "DCC") { + this.DccMethod = method; InitializeComponent(); this.Id = "dcc-file"; this.Header = this.Title = string.Format("{0} [DCC]", target.Name); @@ -113,7 +128,15 @@ public void StartSend(FileInfo file, Action readyCallback) this.FileSize = file.Length; this.Status = FileStatus.Working; - _dcc = new DccXmitSender(_fileInfo, (action) => this.Dispatcher.BeginInvoke(action)); + switch (this.DccMethod) + { + case DccMethod.Send: + _dcc = new DccSendSender(_fileInfo); + break; + case DccMethod.Xmit: + _dcc = new DccXmitSender(_fileInfo); + break; + } _dcc.Connected += dcc_Connected; _dcc.Disconnected += dcc_Disconnected; _dcc.Error += dcc_Error; @@ -187,7 +210,7 @@ public override void Dispose() if (_dcc != null) { - _dcc.Close(); + _dcc.Dispose(); } } @@ -201,7 +224,15 @@ private void Accept(bool forceOverwrite) { this.Status = FileStatus.Working; this.StatusText = "Connecting"; - _dcc = new DccXmitReceiver(_fileInfo, (action) => this.Dispatcher.BeginInvoke(action)) { ForceOverwrite = forceOverwrite, ForceResume = chkForceResume.IsChecked == true }; + switch (this.DccMethod) + { + case DccMethod.Send: + _dcc = new DccSendReceiver(_fileInfo) { ForceOverwrite = forceOverwrite }; + break; + case DccMethod.Xmit: + _dcc = new DccXmitReceiver(_fileInfo) { ForceOverwrite = forceOverwrite, ForceResume = chkForceResume.IsChecked == true }; + break; + } _dcc.Connect(_address, _port); _dcc.Connected += dcc_Connected; _dcc.Disconnected += dcc_Disconnected; @@ -272,6 +303,7 @@ private void dcc_Disconnected(object sender, EventArgs e) this.BytesTransferred = _dcc.BytesTransferred; this.Speed = 0; this.EstimatedTime = 0; + this.Progress = 1f; if (this.BytesTransferred < this.FileSize) { @@ -283,7 +315,7 @@ private void dcc_Disconnected(object sender, EventArgs e) } else { - this.Status = _dcc is DccXmitReceiver ? FileStatus.Received : FileStatus.Sent; + this.Status = (_dcc is DccXmitReceiver || _dcc is DccSendReceiver) ? FileStatus.Received : FileStatus.Sent; this.StatusText = "Finished"; } this.DeletePortForwarding(); @@ -306,13 +338,21 @@ private void btnCancel_Click(object sender, RoutedEventArgs e) this.StatusText = "Cancelled"; if (_dcc != null) { - _dcc.Close(); + _dcc.Dispose(); } } private void btnOpen_Click(object sender, RoutedEventArgs e) { - string path = ((DccXmitReceiver)_dcc).FileSavedAs; + string path = ""; + if (_dcc is DccXmitReceiver) + { + path = ((DccXmitReceiver)_dcc).FileSavedAs; + } + else + { + path = ((DccSendReceiver)_dcc).FileSavedAs; + } if (File.Exists(path)) { App.BrowseTo(path); diff --git a/Floe.UI/HsvColor.cs b/Floe.UI/HsvColor.cs index 2311c54..aaca471 100644 --- a/Floe.UI/HsvColor.cs +++ b/Floe.UI/HsvColor.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows.Media; namespace Floe.UI diff --git a/Floe.UI/Interop/FolderBrowser.cs b/Floe.UI/Interop/FolderBrowser.cs index d463999..8a76611 100644 --- a/Floe.UI/Interop/FolderBrowser.cs +++ b/Floe.UI/Interop/FolderBrowser.cs @@ -1,7 +1,5 @@ using System; using System.Runtime.InteropServices; -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Windows; using System.Windows.Interop; diff --git a/Floe.UI/Properties/AssemblyInfo.cs b/Floe.UI/Properties/AssemblyInfo.cs index cac61dd..b64a2df 100644 --- a/Floe.UI/Properties/AssemblyInfo.cs +++ b/Floe.UI/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; diff --git a/Floe.UI/Resources/Help.txt b/Floe.UI/Resources/Help.txt index f062408..58e5ff3 100644 --- a/Floe.UI/Resources/Help.txt +++ b/Floe.UI/Resources/Help.txt @@ -3,7 +3,8 @@ /BAN - Bans the user from the current channel /CLEAR - Clear the current screen /CTCP [args] - Send CTCP command to a user -/DCC XMIT - Send a file to another user +/DCC SEND - Send a file to another user +/DCC XMIT - Send a file to another user (fast) /DEOP - De-op user in the current channel /DEVOICE - Remove a user's voice in the current channel /HELP - Display this message diff --git a/Floe.UI/Settings/DccSettingsControl.xaml.cs b/Floe.UI/Settings/DccSettingsControl.xaml.cs index 243d9c4..e902dee 100644 --- a/Floe.UI/Settings/DccSettingsControl.xaml.cs +++ b/Floe.UI/Settings/DccSettingsControl.xaml.cs @@ -1,16 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace Floe.UI.Settings { diff --git a/Floe.sln b/Floe.sln index fbca9cc..4ffa770 100644 --- a/Floe.sln +++ b/Floe.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual C# Express 2010 +# Visual Studio 2010 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Floe.Net", "Floe.Net\Floe.Net.csproj", "{1D4AD463-4355-4DA6-B8E6-0E8BB75B50D9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Floe.UI", "Floe.UI\Floe.UI.csproj", "{AC54B841-CB6D-44A0-88C0-EC6ECC0E190C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Floe.Configuration", "Floe.Configuration\Floe.Configuration.csproj", "{75037294-D6D4-42B0-AF4B-FE2F438D0670}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Floe.Audio", "Floe.Audio\Floe.Audio.csproj", "{B3C6437F-718B-440A-96D9-7E0627249948}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,6 +49,16 @@ Global {75037294-D6D4-42B0-AF4B-FE2F438D0670}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {75037294-D6D4-42B0-AF4B-FE2F438D0670}.Release|Mixed Platforms.Build.0 = Release|Any CPU {75037294-D6D4-42B0-AF4B-FE2F438D0670}.Release|x86.ActiveCfg = Release|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Debug|x86.ActiveCfg = Debug|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Release|Any CPU.Build.0 = Release|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B3C6437F-718B-440A-96D9-7E0627249948}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d96fc311440b7b4f6374acfcbadfc958fb0afe58 Mon Sep 17 00:00:00 2001 From: brett Date: Tue, 15 Feb 2011 11:59:46 -0800 Subject: [PATCH 05/49] fix potential UTF-8 decoding bug (where multibyte chars are split between reads) --- Floe.Net/IrcConnection.cs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Floe.Net/IrcConnection.cs b/Floe.Net/IrcConnection.cs index 737b4e2..705b440 100644 --- a/Floe.Net/IrcConnection.cs +++ b/Floe.Net/IrcConnection.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Security; using System.Net.Sockets; +using System.Collections.Generic; using System.Text; using System.Threading; @@ -144,9 +145,9 @@ private void SocketLoop() byte[] readBuffer = new byte[512], writeBuffer = new byte[Encoding.UTF8.GetMaxByteCount(512)]; int count = 0; - var input = new StringBuilder(); + bool gotCR = false; + var input = new List(512); IrcMessage outgoing = null; - char last = '\u0000'; IAsyncResult arr = null, arw = null; while (_tcpClient.Connected) @@ -183,22 +184,27 @@ private void SocketLoop() } else { - foreach (char c in Encoding.UTF8.GetChars(readBuffer, 0, count)) + for (int i = 0; i < count; i++) { - if (c == 0xa && last == 0xd) + switch (readBuffer[i]) { - if (input.Length > 0) - { - var incoming = IrcMessage.Parse(input.ToString()); - this.Dispatch(this.OnMessageReceived, incoming); - input.Clear(); - } + case 0xa: + if (gotCR) + { + var incoming = IrcMessage.Parse(Encoding.UTF8.GetString(input.ToArray())); + this.Dispatch(this.OnMessageReceived, incoming); + input.Clear(); + gotCR = false; + } + break; + case 0xd: + gotCR = true; + break; + default: + gotCR = false; + input.Add(readBuffer[i]); + break; } - else if (c != 0xd && c != 0xa) - { - input.Append(c); - } - last = c; } } break; From ff2a888781787b4e573d4f0da16f26186f02b882 Mon Sep 17 00:00:00 2001 From: brett Date: Wed, 16 Feb 2011 00:18:43 -0800 Subject: [PATCH 06/49] complete DCC chat support --- Floe.Net/Dcc/DccOperation.cs | 10 +- Floe.Net/Dcc/NatHelper.cs | 2 +- Floe.Net/Floe.Net.csproj | 1 + Floe.Net/IrcConnection.cs | 6 +- Floe.UI/Application/App.xaml | 4 + Floe.UI/Application/App.xaml.cs | 19 +- Floe.UI/ChannelWindow/ChannelWindow.xaml.cs | 24 ++- .../ChannelWindow/ChannelWindow_Commands.cs | 4 + Floe.UI/ChatControl/ChatControl.xaml | 22 ++- Floe.UI/ChatControl/ChatControl.xaml.cs | 185 ++++++++++-------- Floe.UI/ChatControl/ChatControl_Commands.cs | 90 +++++++-- Floe.UI/ChatControl/ChatControl_Dcc.cs | 135 +++++++++++++ Floe.UI/ChatControl/ChatControl_Events.cs | 9 +- Floe.UI/ChatWindow/ChatPage.cs | 3 +- Floe.UI/ChatWindow/ChatWindow.xaml | 3 +- Floe.UI/ChatWindow/ChatWindow.xaml.cs | 4 +- Floe.UI/ChatWindow/ChatWindow_Commands.cs | 24 +-- Floe.UI/ChatWindow/ChatWindow_Dcc.cs | 58 ++++-- Floe.UI/ChatWindow/ChatWindow_Events.cs | 18 +- Floe.UI/Floe.UI.csproj | 1 + Floe.UI/Resources/Help.txt | 1 + 21 files changed, 446 insertions(+), 177 deletions(-) create mode 100644 Floe.UI/ChatControl/ChatControl_Dcc.cs diff --git a/Floe.Net/Dcc/DccOperation.cs b/Floe.Net/Dcc/DccOperation.cs index 82adbd3..e27b15a 100644 --- a/Floe.Net/Dcc/DccOperation.cs +++ b/Floe.Net/Dcc/DccOperation.cs @@ -78,11 +78,11 @@ public int Listen(int lowPort, int highPort) { if (lowPort > ushort.MaxValue || lowPort < MinPort) { - throw new ArgumentException("lowPort"); + throw new ArgumentException("Invalid port.", "lowPort"); } if (highPort > ushort.MaxValue || highPort < lowPort) { - throw new ArgumentException("highPort"); + throw new ArgumentException("Invalid port.", "highPort"); } while(true) @@ -161,7 +161,7 @@ public void Connect(IPAddress address, int port) { if (port <= 0 || port > ushort.MaxValue) { - throw new ArgumentException("port"); + throw new ArgumentException("Invalid port."); } this.Address = address; this.Port = port; @@ -182,6 +182,10 @@ public void Connect(IPAddress address, int port) this.OnError(ex); return; } + catch (NullReferenceException) + { + return; + } try { diff --git a/Floe.Net/Dcc/NatHelper.cs b/Floe.Net/Dcc/NatHelper.cs index 007e3de..d9b9fbe 100644 --- a/Floe.Net/Dcc/NatHelper.cs +++ b/Floe.Net/Dcc/NatHelper.cs @@ -124,7 +124,7 @@ public static bool EndDiscover(IAsyncResult ar) var result = ar as AsyncResult; if(result == null) { - throw new ArgumentException("ar"); + throw new ArgumentException("IAsyncResult is not from this operation.", "ar"); } result.AsyncWaitHandle.WaitOne(); return result.IsSuccessful; diff --git a/Floe.Net/Floe.Net.csproj b/Floe.Net/Floe.Net.csproj index aced7fc..a1b395a 100644 --- a/Floe.Net/Floe.Net.csproj +++ b/Floe.Net/Floe.Net.csproj @@ -47,6 +47,7 @@ + diff --git a/Floe.Net/IrcConnection.cs b/Floe.Net/IrcConnection.cs index 705b440..fed44d8 100644 --- a/Floe.Net/IrcConnection.cs +++ b/Floe.Net/IrcConnection.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; -using System.Collections.Generic; using System.Text; using System.Threading; @@ -194,17 +194,15 @@ private void SocketLoop() var incoming = IrcMessage.Parse(Encoding.UTF8.GetString(input.ToArray())); this.Dispatch(this.OnMessageReceived, incoming); input.Clear(); - gotCR = false; } break; case 0xd: - gotCR = true; break; default: - gotCR = false; input.Add(readBuffer[i]); break; } + gotCR = readBuffer[i] == 0xd; } } break; diff --git a/Floe.UI/Application/App.xaml b/Floe.UI/Application/App.xaml index 713299d..cff57ce 100644 --- a/Floe.UI/Application/App.xaml +++ b/Floe.UI/Application/App.xaml @@ -142,6 +142,10 @@ + + - + + + + + + + + + + + + + + + diff --git a/Floe.UI/ListControl/ListControl.xaml.cs b/Floe.UI/ListControl/ListControl.xaml.cs new file mode 100644 index 0000000..b1d2e0d --- /dev/null +++ b/Floe.UI/ListControl/ListControl.xaml.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Floe.Net; + +namespace Floe.UI +{ + public partial class ListControl : ChatPage + { + public class ChannelItem : IComparable + { + public string Name { get; private set; } + public int Count { get; private set; } + public string Topic { get; private set; } + + public ChannelItem(string name, int count, string topic) + { + this.Name = name; + this.Count = count; + this.Topic = topic; + } + + public int CompareTo(ChannelItem other) + { + return string.Compare(this.Name, other.Name); + } + } + + public static readonly DependencyProperty CountProperty = + DependencyProperty.Register("Count", typeof(int), typeof(ListControl)); + public int Count + { + get { return (int)this.GetValue(CountProperty); } + set { this.SetValue(CountProperty, value); } + } + + private List _channels; + + public ListControl(IrcSession session) + : base(ChatPageType.ChannelList, session, null, "chan-list") + { + _channels = new List(); + InitializeComponent(); + this.Header = "Channel List"; + this.Title = string.Format("{0} - {1} - {2} Channel List", App.Product, this.Session.Nickname, this.Session.NetworkName); + this.IsCloseable = false; + this.Session.InfoReceived += new EventHandler(Session_InfoReceived); + + var menu = this.Resources["cmChannels"] as ContextMenu; + if (menu != null) + { + NameScope.SetNameScope(menu, NameScope.GetNameScope(this)); + } + } + + public void ExecuteJoin(object sender, ExecutedRoutedEventArgs e) + { + string channel = e.Parameter as string; + if (!string.IsNullOrEmpty(channel)) + { + this.Session.Join(channel); + } + } + + private void Session_InfoReceived(object sender, IrcInfoEventArgs e) + { + switch (e.Code) + { + case IrcCode.RPL_LIST: + int count; + if (e.Message.Parameters.Count == 4 && + int.TryParse(e.Message.Parameters[2], out count)) + { + _channels.Add(new ChannelItem(e.Message.Parameters[1], count, e.Message.Parameters[3])); + this.Count++; + } + break; + case IrcCode.RPL_LISTEND: + this.IsCloseable = true; + this.Session.InfoReceived -= new EventHandler(Session_InfoReceived); + _channels.Sort(); + foreach (var c in _channels) + { + lstChannels.Items.Add(c); + } + break; + } + } + + private void lstChannels_MouseDoubleClick(object sender, RoutedEventArgs e) + { + var chanItem = ((ListBoxItem)e.Source).Content as ChannelItem; + if (chanItem != null) + { + ChatControl.JoinCommand.Execute(chanItem.Name, this); + } + } + } +} diff --git a/Floe.UI/Resources/Help.txt b/Floe.UI/Resources/Help.txt index cf3537d..02d877a 100644 --- a/Floe.UI/Resources/Help.txt +++ b/Floe.UI/Resources/Help.txt @@ -13,7 +13,7 @@ /INVITE # - Invite a user to a channel /JOIN or /J # [key] - Join a channel /KICK [#channel] [text] - Kick a user from a channel -/LIST [#channel] [target] - List channel information +/LIST [channel[,channel...]] [target] - List channel information /ME or /ACTION - Send an action message /MODE <+/-> - Set or unset modes for yourself /MODE # <+/-> - Set or unset channel modes diff --git a/Floe.UI/Settings/ColorsSettingsControl.xaml b/Floe.UI/Settings/ColorsSettingsControl.xaml index 5d79c7d..db00117 100644 --- a/Floe.UI/Settings/ColorsSettingsControl.xaml +++ b/Floe.UI/Settings/ColorsSettingsControl.xaml @@ -134,6 +134,7 @@ - diff --git a/Floe.UI/ChatControl/ChatControl.xaml b/Floe.UI/ChatControl/ChatControl.xaml index 4937a50..7ed98c1 100644 --- a/Floe.UI/ChatControl/ChatControl.xaml +++ b/Floe.UI/ChatControl/ChatControl.xaml @@ -39,13 +39,16 @@ - + + + + @@ -87,6 +90,8 @@ + + @@ -106,8 +111,7 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + +