From da80a32d5dcf6e9339ccf82f88bcf6c6a947edba Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Sat, 18 Nov 2017 11:49:37 +0100 Subject: [PATCH 1/8] Changed .Net Framework to 4.5 --- Samples/Console/Connect/Connect.csproj | 7 ++- Samples/Console/Connect/app.config | 2 +- Samples/WPF/Connect/Connect.csproj | 8 +++- .../Connect/Properties/Resources.Designer.cs | 46 ++++++++----------- .../Connect/Properties/Settings.Designer.cs | 24 ++++------ Samples/WPF/Connect/app.config | 3 ++ Samples/Web/Connect/Connect.csproj | 16 +++++-- Samples/Web/Connect/web.config | 17 +++++-- Samples/WinForms/Connect/Connect.csproj | 8 +++- .../Connect/Properties/Settings.Designer.cs | 24 ++++------ Samples/WinForms/Connect/app.config | 3 ++ .../Properties/Resources.Designer.cs | 46 ++++++++----------- .../Properties/Settings.Designer.cs | 24 ++++------ .../TS3QueryLib.ClientQuery.TestApp.csproj | 8 +++- TS3QueryLib.ClientQuery.TestApp/app.config | 3 ++ TS3QueryLib.Core.Framework/AsyncHelper.cs | 21 +++++++++ .../TS3QueryLib.Core.Framework.csproj | 6 ++- TS3QueryLib.Web/TS3QueryLib.Web.csproj | 11 +++-- 18 files changed, 159 insertions(+), 118 deletions(-) create mode 100644 Samples/WPF/Connect/app.config create mode 100644 Samples/WinForms/Connect/app.config create mode 100644 TS3QueryLib.ClientQuery.TestApp/app.config create mode 100644 TS3QueryLib.Core.Framework/AsyncHelper.cs diff --git a/Samples/Console/Connect/Connect.csproj b/Samples/Console/Connect/Connect.csproj index b30ead5..bee8674 100644 --- a/Samples/Console/Connect/Connect.csproj +++ b/Samples/Console/Connect/Connect.csproj @@ -9,9 +9,10 @@ Properties Connect Connect - v3.5 + v4.5 512 - Client + + AnyCPU @@ -22,6 +23,7 @@ DEBUG;TRACE prompt 4 + false AnyCPU @@ -31,6 +33,7 @@ TRACE prompt 4 + false diff --git a/Samples/Console/Connect/app.config b/Samples/Console/Connect/app.config index 5044c98..51278a4 100644 --- a/Samples/Console/Connect/app.config +++ b/Samples/Console/Connect/app.config @@ -1,3 +1,3 @@ - + diff --git a/Samples/WPF/Connect/Connect.csproj b/Samples/WPF/Connect/Connect.csproj index d270c2d..b7859bf 100644 --- a/Samples/WPF/Connect/Connect.csproj +++ b/Samples/WPF/Connect/Connect.csproj @@ -10,8 +10,9 @@ Properties Connect Connect - v4.0 - Client + v4.5 + + 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 @@ -29,6 +30,7 @@ DEBUG;TRACE prompt 4 + false x86 @@ -38,6 +40,7 @@ TRACE prompt 4 + false @@ -93,6 +96,7 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/Samples/WPF/Connect/Properties/Resources.Designer.cs b/Samples/WPF/Connect/Properties/Resources.Designer.cs index 15769ff..c8e1a0b 100644 --- a/Samples/WPF/Connect/Properties/Resources.Designer.cs +++ b/Samples/WPF/Connect/Properties/Resources.Designer.cs @@ -1,17 +1,17 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.235 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace Connect.Properties -{ - - +namespace Connect.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace Connect.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Connect.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/Samples/WPF/Connect/Properties/Settings.Designer.cs b/Samples/WPF/Connect/Properties/Settings.Designer.cs index 07e97a9..c597211 100644 --- a/Samples/WPF/Connect/Properties/Settings.Designer.cs +++ b/Samples/WPF/Connect/Properties/Settings.Designer.cs @@ -1,28 +1,24 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.235 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace Connect.Properties -{ - - +namespace Connect.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/Samples/WPF/Connect/app.config b/Samples/WPF/Connect/app.config new file mode 100644 index 0000000..51278a4 --- /dev/null +++ b/Samples/WPF/Connect/app.config @@ -0,0 +1,3 @@ + + + diff --git a/Samples/Web/Connect/Connect.csproj b/Samples/Web/Connect/Connect.csproj index 37591ec..9a47fda 100644 --- a/Samples/Web/Connect/Connect.csproj +++ b/Samples/Web/Connect/Connect.csproj @@ -13,7 +13,7 @@ Properties Connect Connect - v4.0 + v4.5 false SAK SAK @@ -24,6 +24,13 @@ 4.0 + + + + + + + true @@ -33,6 +40,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -41,24 +49,24 @@ TRACE prompt 4 + false + - - - + diff --git a/Samples/Web/Connect/web.config b/Samples/Web/Connect/web.config index 6f48233..b807387 100644 --- a/Samples/Web/Connect/web.config +++ b/Samples/Web/Connect/web.config @@ -1,8 +1,15 @@  + + + + + + \ No newline at end of file diff --git a/Samples/WinForms/Connect/Connect.csproj b/Samples/WinForms/Connect/Connect.csproj index d1a7e6f..0b2af39 100644 --- a/Samples/WinForms/Connect/Connect.csproj +++ b/Samples/WinForms/Connect/Connect.csproj @@ -10,8 +10,9 @@ Properties Connect Connect - v4.0 - Client + v4.5 + + 512 SAK SAK @@ -27,6 +28,7 @@ DEBUG;TRACE prompt 4 + false x86 @@ -36,6 +38,7 @@ TRACE prompt 4 + false @@ -63,6 +66,7 @@ MainForm.cs + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/Samples/WinForms/Connect/Properties/Settings.Designer.cs b/Samples/WinForms/Connect/Properties/Settings.Designer.cs index 07e97a9..c597211 100644 --- a/Samples/WinForms/Connect/Properties/Settings.Designer.cs +++ b/Samples/WinForms/Connect/Properties/Settings.Designer.cs @@ -1,28 +1,24 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.235 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace Connect.Properties -{ - - +namespace Connect.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/Samples/WinForms/Connect/app.config b/Samples/WinForms/Connect/app.config new file mode 100644 index 0000000..51278a4 --- /dev/null +++ b/Samples/WinForms/Connect/app.config @@ -0,0 +1,3 @@ + + + diff --git a/TS3QueryLib.ClientQuery.TestApp/Properties/Resources.Designer.cs b/TS3QueryLib.ClientQuery.TestApp/Properties/Resources.Designer.cs index 050a933..e3a4df4 100644 --- a/TS3QueryLib.ClientQuery.TestApp/Properties/Resources.Designer.cs +++ b/TS3QueryLib.ClientQuery.TestApp/Properties/Resources.Designer.cs @@ -1,17 +1,17 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.235 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace TS3QueryLib.ClientQuery.TestApp.Properties -{ - - +namespace TS3QueryLib.ClientQuery.TestApp.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace TS3QueryLib.ClientQuery.TestApp.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TS3QueryLib.ClientQuery.TestApp.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/TS3QueryLib.ClientQuery.TestApp/Properties/Settings.Designer.cs b/TS3QueryLib.ClientQuery.TestApp/Properties/Settings.Designer.cs index e5a7913..e9eb947 100644 --- a/TS3QueryLib.ClientQuery.TestApp/Properties/Settings.Designer.cs +++ b/TS3QueryLib.ClientQuery.TestApp/Properties/Settings.Designer.cs @@ -1,28 +1,24 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.235 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace TS3QueryLib.ClientQuery.TestApp.Properties -{ - - +namespace TS3QueryLib.ClientQuery.TestApp.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/TS3QueryLib.ClientQuery.TestApp/TS3QueryLib.ClientQuery.TestApp.csproj b/TS3QueryLib.ClientQuery.TestApp/TS3QueryLib.ClientQuery.TestApp.csproj index fbb4c4a..617ae43 100644 --- a/TS3QueryLib.ClientQuery.TestApp/TS3QueryLib.ClientQuery.TestApp.csproj +++ b/TS3QueryLib.ClientQuery.TestApp/TS3QueryLib.ClientQuery.TestApp.csproj @@ -10,8 +10,9 @@ Properties TS3QueryLib.ClientQuery.TestApp TS3QueryLib.ClientQuery.TestApp - v4.0 - Client + v4.5 + + 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 @@ -29,6 +30,7 @@ DEBUG;TRACE prompt 4 + false x86 @@ -38,6 +40,7 @@ TRACE prompt 4 + false @@ -94,6 +97,7 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/TS3QueryLib.ClientQuery.TestApp/app.config b/TS3QueryLib.ClientQuery.TestApp/app.config new file mode 100644 index 0000000..51278a4 --- /dev/null +++ b/TS3QueryLib.ClientQuery.TestApp/app.config @@ -0,0 +1,3 @@ + + + diff --git a/TS3QueryLib.Core.Framework/AsyncHelper.cs b/TS3QueryLib.Core.Framework/AsyncHelper.cs new file mode 100644 index 0000000..76333b2 --- /dev/null +++ b/TS3QueryLib.Core.Framework/AsyncHelper.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace TS3QueryLib.Core +{ + public static class AsyncHelper + { + private static TaskFactory TaskFactory { get; } = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); + + public static TResult RunSync(Func> func) + { + return TaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); + } + + public static void RunSync(Func func) + { + TaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult(); + } + } +} diff --git a/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj b/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj index ae7608c..91fcfaf 100644 --- a/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj +++ b/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj @@ -10,7 +10,7 @@ Properties TS3QueryLib.Core TS3QueryLib.Core.Framework - v3.5 + v4.5 512 SAK SAK @@ -31,6 +31,7 @@ false false true + true @@ -42,6 +43,7 @@ 4 bin\Debug\TS3QueryLib.Core.Framework.XML AllRules.ruleset + false pdbonly @@ -52,6 +54,7 @@ 4 bin\Release\TS3QueryLib.Core.Framework.XML AllRules.ruleset + false @@ -63,6 +66,7 @@ + diff --git a/TS3QueryLib.Web/TS3QueryLib.Web.csproj b/TS3QueryLib.Web/TS3QueryLib.Web.csproj index 2baa592..c7d1a05 100644 --- a/TS3QueryLib.Web/TS3QueryLib.Web.csproj +++ b/TS3QueryLib.Web/TS3QueryLib.Web.csproj @@ -13,7 +13,7 @@ Properties TS3QueryLib.Web TS3QueryLib.Web - v3.5 + v4.5 SAK SAK SAK @@ -23,6 +23,7 @@ 4.0 + true @@ -33,6 +34,7 @@ prompt 4 AllRules.ruleset + false pdbonly @@ -42,14 +44,16 @@ prompt 4 AllRules.ruleset + false - + + + - @@ -57,6 +61,7 @@ + From bac613f10d0cb40c3a630c9c3ba4b6a37bb7a85e Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Sat, 18 Nov 2017 12:47:15 +0100 Subject: [PATCH 2/8] Added AwaitableQueryDispatcher (much faster) --- Deployment/Changelog.txt | 2 + .../AwaitableQueryDispatcher.cs | 309 ++++++++++++++++++ .../TS3QueryLib.Core.Framework.csproj | 1 + 3 files changed, 312 insertions(+) create mode 100644 TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs diff --git a/Deployment/Changelog.txt b/Deployment/Changelog.txt index 3aebc75..8d8e3fa 100644 --- a/Deployment/Changelog.txt +++ b/Deployment/Changelog.txt @@ -1,6 +1,8 @@ Version 2.0.0.0 ========================================== +- Added AwaitableQueryDispatcher - Dropped support for Silverligt and Windows Phone 7 +- Changed minimum .Net Framework Version to 4.5 Version 1.2.2.0 ========================================== diff --git a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs new file mode 100644 index 0000000..3c33290 --- /dev/null +++ b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using TS3QueryLib.Core.Common; +using TS3QueryLib.Core.Common.Responses; + +namespace TS3QueryLib.Core +{ + public class AwaitableQueryDispatcher : IQueryDispatcher + { + #region Events + + /// + /// Raised when the connection to the server was closed + /// + public event EventHandler> ConnectionClosed; + + public bool IsDisposed { get; private set; } + public int? LastServerConnectionHandlerId { get; private set; } + + /// + /// Raised when a notification was received + /// + public event EventHandler> NotificationReceived; + + /// + /// Raised when a ban was detected + /// + public event EventHandler> BanDetected; + + #endregion + + #region Properties + + public string Host { get; } + public int Port { get; } + protected TimeSpan? KeepAliveInterval { get; } + public bool Connected { get; protected set; } + + private List ReceivedLines { get; } = new List(); + private bool AtLeastOneResponseReceived { get; set; } + private ConcurrentQueue MessageResponses { get; } = new ConcurrentQueue(); + private Task ReadLoopTask { get; set; } + private Task KeepAliveTask { get; set; } + + private TcpClient Client { get; set; } + private StreamReader ClientReader { get; set; } + private StreamWriter ClientWriter { get; set; } + private NetworkStream ClientStream { get; set; } + protected SynchronizationContext SyncContext { get; set; } + + #endregion + + #region Constructor + + /// + /// Creates an instance of the current class + /// + /// The host to connect to + /// The port to connect to + /// The keep alive interval used to send heart beats in a specific interval to the server to not get timed out (disconnected) + /// The synchronization context on which to raise events. + public AwaitableQueryDispatcher(string host = null, ushort? port = null, TimeSpan? keepAliveInterval = null, SynchronizationContext synchronizationContext = null) + { + Host = host ?? "localhost"; + Port = port ?? 10011; + KeepAliveInterval = keepAliveInterval; + SyncContext = synchronizationContext ?? SynchronizationContext.Current; + } + + #endregion + + #region Public Methods + + public ConnectResponse Connect() + { + return AsyncHelper.RunSync(ConnectAsync); + } + + public async Task ConnectAsync() + { + if (Client != null) + return new ConnectResponse(message: "Already connected!"); + + Client = new TcpClient(); + await Client.ConnectAsync(Host, Port).ConfigureAwait(false); + + if (!Client.Connected) + throw new IOException($"Could not connect to {Host} on port {Port}."); + + ReceivedLines.Clear(); + AtLeastOneResponseReceived = false; + ClientStream = Client.GetStream(); + ClientReader = new StreamReader(ClientStream); + ClientWriter = new StreamWriter(ClientStream) { NewLine = "\n" }; + + string message = await ReadLineAsync().ConfigureAwait(false); + + QueryType queryType; + + if (message.StartsWith("TS3", StringComparison.OrdinalIgnoreCase)) + { + queryType = QueryType.Server; + } + else if (message.StartsWith("TS3 Client", StringComparison.OrdinalIgnoreCase)) + { + queryType = QueryType.Client; + } + else + { + string statusMessage = $"Invalid greeting received: {message}"; + DisconnectForced(statusMessage); + return new ConnectResponse(statusMessage); + } + + Connected = true; + ReadLoopTask = Task.Factory.StartNew(ReadLoop, TaskCreationOptions.LongRunning); + KeepAliveTask = Task.Factory.StartNew(KeepAliveLoop, TaskCreationOptions.LongRunning); + return new ConnectResponse(message, queryType, true); + } + + public string Send(string messageToSend) + { + return AsyncHelper.RunSync(() => SendAsync(messageToSend)); + } + + public async Task SendAsync(string messageToSend) + { + await SendAsync(ClientWriter, messageToSend); + + do + { + if (MessageResponses.TryDequeue(out var result)) + return result; + + await Task.Delay(TimeSpan.FromMilliseconds(10)).ConfigureAwait(false); + } while (Connected); + + return null; + } + + protected static async Task SendAsync(StreamWriter writer, string messageToSend) + { + await writer.WriteLineAsync(messageToSend).ConfigureAwait(false); + await writer.FlushAsync().ConfigureAwait(false); + } + + public void Disconnect() + { + DisconnectForced(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public string Dispatch(string commandText) + { + return Send(commandText); + } + + #endregion + + #region Non Public Methods + + protected async void KeepAliveLoop() + { + while (Client != null && KeepAliveInterval.HasValue) + { + await Task.Delay(KeepAliveInterval.Value); + await SendAsync(ClientWriter, "\n"); + } + } + + protected async void ReadLoop() + { + while (Client != null && Client.Connected) + { + string message = await ReadLineAsync(false).ConfigureAwait(false); + + if (message == null) + continue; + + if (message.StartsWith("error", StringComparison.CurrentCultureIgnoreCase)) + { + if (!AtLeastOneResponseReceived) + { + AtLeastOneResponseReceived = true; + // Remove welcome messages after connect + ReceivedLines.Clear(); + } + + string responseText = string.Join("\r\n", ReceivedLines.Concat(new[] { message })); + MessageResponses.Enqueue(responseText); + ReceivedLines.Clear(); + + SimpleResponse response = SimpleResponse.Parse(responseText); + + if (response.IsBanned) + { + BanDetected?.Invoke(this, new EventArgs(response)); + DisconnectForced("Banned!"); + return; + } + } + else if (message.StartsWith("notify", StringComparison.CurrentCultureIgnoreCase)) + { + ThreadPool.QueueUserWorkItem(OnNotificationReceived, message); + } + else + { + if (!AtLeastOneResponseReceived) + { + const string LastServerConnectionHandlerIdText = "selected schandlerid="; + + if (message.StartsWith(LastServerConnectionHandlerIdText, StringComparison.InvariantCultureIgnoreCase) && int.TryParse(message.Substring(LastServerConnectionHandlerIdText.Length).Trim(), out int handlerId)) + LastServerConnectionHandlerId = handlerId; + } + + ReceivedLines.Add(message); + } + } + } + + protected async Task ReadLineAsync(bool throwOnEmptyMessage = true) + { + string message = await ClientReader.ReadLineAsync().ConfigureAwait(false); + + if (message != null) + return message; + + DisconnectForced("Empty message received from server."); + + if (throwOnEmptyMessage) + throw new InvalidOperationException("Received no message. Socket got disconnected."); + + return null; + } + + protected void OnNotificationReceived(object notificationText) + { + if (NotificationReceived != null) + SyncContext.PostEx(p => NotificationReceived(((object[])p)[0], new EventArgs(Convert.ToString(((object[])p)[1]))), new[] { this, notificationText }); + } + + private void DisconnectForced(string reason = null) + { + bool clientWasConnected = Client?.Connected == true; + ReceivedLines.Clear(); + Client?.Close(); + ClientStream?.Dispose(); + ClientReader?.Dispose(); + ClientWriter?.Dispose(); + + Client = null; + ClientStream = null; + ClientReader = null; + ClientWriter = null; + + Connected = false; + ReadLoopTask = null; + KeepAliveTask = null; + + if (clientWasConnected) + ConnectionClosed?.Invoke(this, new EventArgs(reason)); + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + DisconnectForced(); + } + + IsDisposed = true; + } + + #endregion + + #region Embedded Types + + public class ConnectResponse + { + public string Greeting { get; } + public QueryType? QueryType { get; } + + public bool Success { get; } + public string Message { get; set; } + + public ConnectResponse(string greeting = null, QueryType? queryType = null, bool success = false, string message = null) + { + Greeting = greeting; + QueryType = queryType; + Success = success; + Message = message; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj b/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj index 91fcfaf..0fad9e4 100644 --- a/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj +++ b/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj @@ -68,6 +68,7 @@ + From 3c9fee5a89d0776b0a23fba848b684b6e74bf86f Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Sat, 18 Nov 2017 13:50:46 +0100 Subject: [PATCH 3/8] Added additional error handling for parse errors --- Deployment/Changelog.txt | 1 + Deployment/Deploy.target | 2 +- .../AwaitableQueryDispatcher.cs | 16 +++++++--- .../Common/Exceptions/ParseException.cs | 24 +++++++++++++++ .../Common/Responses/ResponseBase.cs | 30 ++++++++++++------- .../TS3QueryLib.Core.Framework.csproj | 1 + 6 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 TS3QueryLib.Core.Framework/Common/Exceptions/ParseException.cs diff --git a/Deployment/Changelog.txt b/Deployment/Changelog.txt index 8d8e3fa..254ac7e 100644 --- a/Deployment/Changelog.txt +++ b/Deployment/Changelog.txt @@ -3,6 +3,7 @@ Version 2.0.0.0 - Added AwaitableQueryDispatcher - Dropped support for Silverligt and Windows Phone 7 - Changed minimum .Net Framework Version to 4.5 +- Added more detailed exception in cases when there is a response parsing error Version 1.2.2.0 ========================================== diff --git a/Deployment/Deploy.target b/Deployment/Deploy.target index 40efb85..85c0abc 100644 --- a/Deployment/Deploy.target +++ b/Deployment/Deploy.target @@ -5,7 +5,7 @@ .. Release - 1.2.2.0 + 2.0.0.0 $(ReleaseVersion.Replace(".", "_")) TS3QueryLib.Net.V$(ReleaseVersionEscaped) Releases\$(ReleaseVersionEscaped) diff --git a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs index 3c33290..1cb9101 100644 --- a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs +++ b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Sockets; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using TS3QueryLib.Core.Common; @@ -146,8 +147,14 @@ public async Task SendAsync(string messageToSend) protected static async Task SendAsync(StreamWriter writer, string messageToSend) { - await writer.WriteLineAsync(messageToSend).ConfigureAwait(false); - await writer.FlushAsync().ConfigureAwait(false); + ConfiguredTaskAwaitable? writeLineAwaitable = writer?.WriteLineAsync(messageToSend).ConfigureAwait(false); + + if (writeLineAwaitable.HasValue) + await writeLineAwaitable.Value; + + ConfiguredTaskAwaitable? flushAwaitable = writer?.FlushAsync().ConfigureAwait(false); + if (flushAwaitable.HasValue) + await flushAwaitable.Value; } public void Disconnect() @@ -197,7 +204,7 @@ protected async void ReadLoop() ReceivedLines.Clear(); } - string responseText = string.Join("\r\n", ReceivedLines.Concat(new[] { message })); + string responseText = string.Join("\n\r", ReceivedLines.Concat(new[] { message })); MessageResponses.Enqueue(responseText); ReceivedLines.Clear(); @@ -231,7 +238,8 @@ protected async void ReadLoop() protected async Task ReadLineAsync(bool throwOnEmptyMessage = true) { - string message = await ClientReader.ReadLineAsync().ConfigureAwait(false); + ConfiguredTaskAwaitable? readLineAwaitable = ClientReader?.ReadLineAsync().ConfigureAwait(false); + string message = readLineAwaitable.HasValue ? await readLineAwaitable.Value : null; if (message != null) return message; diff --git a/TS3QueryLib.Core.Framework/Common/Exceptions/ParseException.cs b/TS3QueryLib.Core.Framework/Common/Exceptions/ParseException.cs new file mode 100644 index 0000000..5dff4e1 --- /dev/null +++ b/TS3QueryLib.Core.Framework/Common/Exceptions/ParseException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace TS3QueryLib.Core.Common.Exceptions +{ + public class ParseException : Exception + { + public ParseException() + { + } + + public ParseException(string message) : base(message) + { + } + + public ParseException(string message, Exception innerException) : base(message, innerException) + { + } + + protected ParseException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/TS3QueryLib.Core.Framework/Common/Responses/ResponseBase.cs b/TS3QueryLib.Core.Framework/Common/Responses/ResponseBase.cs index 25e418f..7dd7d6e 100644 --- a/TS3QueryLib.Core.Framework/Common/Responses/ResponseBase.cs +++ b/TS3QueryLib.Core.Framework/Common/Responses/ResponseBase.cs @@ -1,5 +1,6 @@ using System; using TS3QueryLib.Core.CommandHandling; +using TS3QueryLib.Core.Common.Exceptions; namespace TS3QueryLib.Core.Common.Responses { @@ -23,21 +24,28 @@ public abstract class ResponseBase : IDump, IResponse where T : ResponseBase< public static T Parse(string response, params object[] additionalStates) { - T instance = (T) Activator.CreateInstance(typeof(T)); - instance.ResponseText = response; - - if (response != null) + try { - string body, statusLine; - SplitResponse(response, out body, out statusLine); - instance.BodyText = body; - instance.StatusText = statusLine; + T instance = (T)Activator.CreateInstance(typeof(T)); + instance.ResponseText = response; + + if (response != null) + { + SplitResponse(response, out var body, out var statusLine); + instance.BodyText = body; + instance.StatusText = statusLine; + + instance.DetermineErrorDetails(statusLine); + instance.FillFrom(response, additionalStates); + } - instance.DetermineErrorDetails(statusLine); - instance.FillFrom(response, additionalStates); + return instance; + } + catch (Exception e) + { + throw new ParseException($"Error while trying to parse the response.\n\nRaw-Response:"+response, e); } - return instance; } #endregion diff --git a/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj b/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj index 0fad9e4..764bc02 100644 --- a/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj +++ b/TS3QueryLib.Core.Framework/TS3QueryLib.Core.Framework.csproj @@ -83,6 +83,7 @@ + From e0caf3a661df038f4750b4b1d584a203a2fccf48 Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Sat, 18 Nov 2017 16:31:43 +0100 Subject: [PATCH 4/8] Fixed parallel send problem --- .../AwaitableQueryDispatcher.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs index 1cb9101..c89b0cb 100644 --- a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs +++ b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs @@ -54,7 +54,8 @@ public class AwaitableQueryDispatcher : IQueryDispatcher private StreamWriter ClientWriter { get; set; } private NetworkStream ClientStream { get; set; } protected SynchronizationContext SyncContext { get; set; } - + private SemaphoreSlim SendLock { get; } = new SemaphoreSlim(1, 1); + #endregion #region Constructor @@ -132,17 +133,26 @@ public string Send(string messageToSend) public async Task SendAsync(string messageToSend) { - await SendAsync(ClientWriter, messageToSend); + await SendLock.WaitAsync(); - do + try { - if (MessageResponses.TryDequeue(out var result)) - return result; + await SendAsync(ClientWriter, messageToSend); - await Task.Delay(TimeSpan.FromMilliseconds(10)).ConfigureAwait(false); - } while (Connected); + do + { + if (MessageResponses.TryDequeue(out var result)) + return result; - return null; + await Task.Delay(TimeSpan.FromMilliseconds(10)).ConfigureAwait(false); + } while (Connected); + + return null; + } + finally + { + SendLock.Release(); + } } protected static async Task SendAsync(StreamWriter writer, string messageToSend) @@ -182,7 +192,7 @@ protected async void KeepAliveLoop() while (Client != null && KeepAliveInterval.HasValue) { await Task.Delay(KeepAliveInterval.Value); - await SendAsync(ClientWriter, "\n"); + await SendAsync("\n"); } } From 2f73aa85cc5e43969bdb13299d26f122fab3aa72 Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Fri, 24 Nov 2017 16:10:07 +0100 Subject: [PATCH 5/8] Change KeepAliveLoop --- TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs index c89b0cb..6041a03 100644 --- a/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs +++ b/TS3QueryLib.Core.Framework/AwaitableQueryDispatcher.cs @@ -192,7 +192,7 @@ protected async void KeepAliveLoop() while (Client != null && KeepAliveInterval.HasValue) { await Task.Delay(KeepAliveInterval.Value); - await SendAsync("\n"); + await SendAsync("version"); } } From 4f01510f7168a9a4fbc1a8dbdaec61408ce3d971 Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Fri, 24 Nov 2017 16:59:48 +0100 Subject: [PATCH 6/8] Added web.config --- TS3QueryLib.Web/TS3QueryLib.Web.csproj | 13 +++---------- TS3QueryLib.Web/{Web_template.config => Web.config} | 0 2 files changed, 3 insertions(+), 10 deletions(-) rename TS3QueryLib.Web/{Web_template.config => Web.config} (100%) diff --git a/TS3QueryLib.Web/TS3QueryLib.Web.csproj b/TS3QueryLib.Web/TS3QueryLib.Web.csproj index c7d1a05..85ef542 100644 --- a/TS3QueryLib.Web/TS3QueryLib.Web.csproj +++ b/TS3QueryLib.Web/TS3QueryLib.Web.csproj @@ -69,13 +69,6 @@ - - - Web.config - - - Web.config - @@ -97,15 +90,15 @@ - - - {FFD6FC39-DCD3-48CC-9E43-FAD303A7E58F} TS3QueryLib.Core.Framework + + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/TS3QueryLib.Web/Web_template.config b/TS3QueryLib.Web/Web.config similarity index 100% rename from TS3QueryLib.Web/Web_template.config rename to TS3QueryLib.Web/Web.config From d397d661c7b8fe9e471a1938278e9fbe715ae956 Mon Sep 17 00:00:00 2001 From: Jens Hofmann Date: Fri, 24 Nov 2017 20:59:07 +0100 Subject: [PATCH 7/8] Removed old web project --- TS3QueryLib.Net.sln | 14 - TS3QueryLib.Web/ClientQueryTesting.aspx | 16 - TS3QueryLib.Web/ClientQueryTesting.aspx.cs | 51 - .../ClientQueryTesting.aspx.designer.cs | 26 - TS3QueryLib.Web/Default.aspx | 16 - TS3QueryLib.Web/Default.aspx.cs | 105 - TS3QueryLib.Web/Default.aspx.designer.cs | 26 - TS3QueryLib.Web/Properties/AssemblyInfo.cs | 35 - TS3QueryLib.Web/Scripts/jquery-1.3.2-vsdoc.js | 6365 ----------------- TS3QueryLib.Web/Scripts/jquery-1.3.2.js | 4593 ------------ TS3QueryLib.Web/Scripts/jquery-1.3.2.min.js | 236 - TS3QueryLib.Web/TS3QueryLib.Web.csproj | 127 - TS3QueryLib.Web/Web.config | 120 - 13 files changed, 11730 deletions(-) delete mode 100644 TS3QueryLib.Web/ClientQueryTesting.aspx delete mode 100644 TS3QueryLib.Web/ClientQueryTesting.aspx.cs delete mode 100644 TS3QueryLib.Web/ClientQueryTesting.aspx.designer.cs delete mode 100644 TS3QueryLib.Web/Default.aspx delete mode 100644 TS3QueryLib.Web/Default.aspx.cs delete mode 100644 TS3QueryLib.Web/Default.aspx.designer.cs delete mode 100644 TS3QueryLib.Web/Properties/AssemblyInfo.cs delete mode 100644 TS3QueryLib.Web/Scripts/jquery-1.3.2-vsdoc.js delete mode 100644 TS3QueryLib.Web/Scripts/jquery-1.3.2.js delete mode 100644 TS3QueryLib.Web/Scripts/jquery-1.3.2.min.js delete mode 100644 TS3QueryLib.Web/TS3QueryLib.Web.csproj delete mode 100644 TS3QueryLib.Web/Web.config diff --git a/TS3QueryLib.Net.sln b/TS3QueryLib.Net.sln index 8761d40..da3bed2 100644 --- a/TS3QueryLib.Net.sln +++ b/TS3QueryLib.Net.sln @@ -21,8 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WPF", "WPF", "{C5C1D6E9-455 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TS3QueryLib.Core.Framework", "TS3QueryLib.Core.Framework\TS3QueryLib.Core.Framework.csproj", "{FFD6FC39-DCD3-48CC-9E43-FAD303A7E58F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TS3QueryLib.Web", "TS3QueryLib.Web\TS3QueryLib.Web.csproj", "{1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TS3ServiceWrapper", "TS3ServiceWrapper\TS3ServiceWrapper.csproj", "{2EE4F0C2-E4FF-4C0C-8294-E7FF4DA23DA2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connect", "Samples\WPF\Connect\Connect.csproj", "{71A164BB-5E30-45DE-8136-DF1A84AF3FDC}" @@ -61,18 +59,6 @@ Global {FFD6FC39-DCD3-48CC-9E43-FAD303A7E58F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {FFD6FC39-DCD3-48CC-9E43-FAD303A7E58F}.Release|Mixed Platforms.Build.0 = Release|Any CPU {FFD6FC39-DCD3-48CC-9E43-FAD303A7E58F}.Release|x86.ActiveCfg = Release|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Debug|ARM.ActiveCfg = Debug|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Debug|x86.ActiveCfg = Debug|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Release|Any CPU.Build.0 = Release|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Release|ARM.ActiveCfg = Release|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {1845DE34-CF37-46ED-B6B2-E73CB4E5A4A6}.Release|x86.ActiveCfg = Release|Any CPU {2EE4F0C2-E4FF-4C0C-8294-E7FF4DA23DA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2EE4F0C2-E4FF-4C0C-8294-E7FF4DA23DA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EE4F0C2-E4FF-4C0C-8294-E7FF4DA23DA2}.Debug|ARM.ActiveCfg = Debug|Any CPU diff --git a/TS3QueryLib.Web/ClientQueryTesting.aspx b/TS3QueryLib.Web/ClientQueryTesting.aspx deleted file mode 100644 index 71b6973..0000000 --- a/TS3QueryLib.Web/ClientQueryTesting.aspx +++ /dev/null @@ -1,16 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ClientQueryTesting.aspx.cs" Inherits="TS3QueryLib.Web.ClientQueryTesting" %> - - - - - - Client Query Testing - - -
-
- -
-
- - diff --git a/TS3QueryLib.Web/ClientQueryTesting.aspx.cs b/TS3QueryLib.Web/ClientQueryTesting.aspx.cs deleted file mode 100644 index a1d37f4..0000000 --- a/TS3QueryLib.Web/ClientQueryTesting.aspx.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Text; -using TS3QueryLib.Core; -using TS3QueryLib.Core.Client; -using TS3QueryLib.Core.Common; - -namespace TS3QueryLib.Web -{ - public partial class ClientQueryTesting : System.Web.UI.Page - { - protected void Page_Load(object sender, EventArgs e) - { - DateTime start = DateTime.Now; - RunQueries(queryRunner => - { - StringBuilder sb = new StringBuilder(); - sb.Append("
");
-                sb.Append(queryRunner.SendWhoAmI().GetDumpString(true));
-                sb.Append(queryRunner.GetChannelList(true).GetDumpString(true));
-                sb.Append("
"); - - AppendToOutput(sb.ToString()); - }); - Response.Write((DateTime.Now - start)); - } - - protected void RunQueries(Action action) - { - if (action == null) - throw new ArgumentNullException("action"); - - - using (QueryRunner queryRunner = new QueryRunner(new SyncTcpDispatcher("localhost", 25639))) // host and port - { - // connection to the TS3-Server is established with the first query command - - action(queryRunner); - } - } - - private void AppendToOutput(IDump dump) - { - AppendToOutput(dump.GetDumpString()); - } - - private void AppendToOutput(string message) - { - Response.Write(string.Format("
{0}

", message)); - } - } -} \ No newline at end of file diff --git a/TS3QueryLib.Web/ClientQueryTesting.aspx.designer.cs b/TS3QueryLib.Web/ClientQueryTesting.aspx.designer.cs deleted file mode 100644 index c37a8bd..0000000 --- a/TS3QueryLib.Web/ClientQueryTesting.aspx.designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TS3QueryLib.Web -{ - - - public partial class ClientQueryTesting - { - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - } -} diff --git a/TS3QueryLib.Web/Default.aspx b/TS3QueryLib.Web/Default.aspx deleted file mode 100644 index 5c50cbc..0000000 --- a/TS3QueryLib.Web/Default.aspx +++ /dev/null @@ -1,16 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TS3QueryLib.Web._Default" %> - - - - - - - - -
-
- -
-
- - diff --git a/TS3QueryLib.Web/Default.aspx.cs b/TS3QueryLib.Web/Default.aspx.cs deleted file mode 100644 index dd0d525..0000000 --- a/TS3QueryLib.Web/Default.aspx.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Globalization; -using System.Text; -using TS3QueryLib.Core; -using TS3QueryLib.Core.Common; -using TS3QueryLib.Core.Common.Responses; -using TS3QueryLib.Core.Server; -using TS3QueryLib.Core.Server.Entities; -using TS3QueryLib.Core.Server.Responses; - -namespace TS3QueryLib.Web -{ - public partial class _Default : System.Web.UI.Page - { - /* things to check for RC1: -+ serveredit and serverinfo command can be used with use 0 to modify and display - virtualserver template values (serverinfo display is different from normal output!) - */ - - protected void Page_Load(object sender, EventArgs e) - { - DateTime start = DateTime.Now; - RunQueries(queryRunner => - { - queryRunner.SelectVirtualServerById(1); // select server with id 1 and show a dump-output of the response in a textbox - - List channelTree = queryRunner.Utils.GetChannelTree(); - StringBuilder sb = new StringBuilder(); - sb.Append("
");
-                AddChannelTree(channelTree, 0, sb);
-                sb.Append(queryRunner.GetChannelList(true).GetDumpString(true));
-                sb.Append("
"); - - AppendToOutput(sb.ToString()); - }); - Response.Write((DateTime.Now - start)); - } - - protected void RunQueries(Action action) - { - if (action == null) - throw new ArgumentNullException("action"); - - string host = ConfigurationManager.AppSettings["host"]; - - if (host == null) - throw new ConfigurationErrorsException("host could not be found in web.config"); - - string portString = ConfigurationManager.AppSettings["port"]; - - if (portString == null) - throw new ConfigurationErrorsException("port could not be found in web.config"); - - ushort port; - - if (!ushort.TryParse(portString, NumberStyles.Integer, CultureInfo.InvariantCulture, out port)) - throw new ConfigurationErrorsException("port in web.config has invalid an invalid format."); - - string login = ConfigurationManager.AppSettings["login"]; - string password = ConfigurationManager.AppSettings["password"]; - - - using (QueryRunner queryRunner = new QueryRunner(new SyncTcpDispatcher(host, port))) // host and port - { - // connection to the TS3-Server is established with the first query command - - if (!login.IsNullOrTrimmedEmpty() && !password.IsNullOrTrimmedEmpty()) - { - SimpleResponse loginResponse = queryRunner.Login(login, password); // login using the provided username and password and show a dump-output of the response in a textbox - - if (loginResponse.IsErroneous) - throw new NotSupportedException("Login failed for the given username and password!"); - } - - action(queryRunner); - } - } - - private static void AddChannelTree(IEnumerable channelTree, int depth, StringBuilder result) - { - foreach (ChannelTreeItem channelTreeItem in channelTree) - { - result.Append(string.Empty.PadLeft(depth, '\t') + "@" + channelTreeItem.Channel.Name +"\n"); - AddChannelTree(channelTreeItem.Children, depth + 1, result); - - foreach (ClientListEntry clientListEntry in channelTreeItem.Clients) - { - result.Append(string.Empty.PadLeft(depth + 1, '\t') + "-" + clientListEntry.Nickname + "\n"); - } - } - } - - private void AppendToOutput(IDump dump) - { - AppendToOutput(dump.GetDumpString()); - } - - private void AppendToOutput(string message) - { - Response.Write(string.Format("
{0}

", message)); - } - } -} \ No newline at end of file diff --git a/TS3QueryLib.Web/Default.aspx.designer.cs b/TS3QueryLib.Web/Default.aspx.designer.cs deleted file mode 100644 index a51cc01..0000000 --- a/TS3QueryLib.Web/Default.aspx.designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TS3QueryLib.Web -{ - - - public partial class _Default - { - - /// - /// form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm form1; - } -} diff --git a/TS3QueryLib.Web/Properties/AssemblyInfo.cs b/TS3QueryLib.Web/Properties/AssemblyInfo.cs deleted file mode 100644 index 6fb64bd..0000000 --- a/TS3QueryLib.Web/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -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("TS3QueryLib.Web")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("TS3QueryLib.Web")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] -[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("77f4c9ac-fb26-45b6-b569-096e31fdc35a")] - -// 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 Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TS3QueryLib.Web/Scripts/jquery-1.3.2-vsdoc.js b/TS3QueryLib.Web/Scripts/jquery-1.3.2-vsdoc.js deleted file mode 100644 index cb690f1..0000000 --- a/TS3QueryLib.Web/Scripts/jquery-1.3.2-vsdoc.js +++ /dev/null @@ -1,6365 +0,0 @@ -/* - * This file has been commented to support Visual Studio Intellisense. - * You should not use this file at runtime inside the browser--it is only - * intended to be used only for design-time IntelliSense. Please use the - * standard jQuery library for all production use. - * - * Comment version: 1.3.2b - */ - -/* - * jQuery JavaScript Library v1.3.2 - * http://jquery.com/ - * - * Copyright (c) 2009 John Resig - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) - * Revision: 6246 - */ - -(function(){ - -var - // Will speed up references to window, and allows munging its name. - window = this, - // Will speed up references to undefined, and allows munging its name. - undefined, - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - // Map over the $ in case of overwrite - _$ = window.$, - - jQuery = window.jQuery = window.$ = function(selector, context) { - /// - /// 1: $(expression, context) - This function accepts a string containing a CSS selector which is then used to match a set of elements. - /// 2: $(html) - Create DOM elements on-the-fly from the provided String of raw HTML. - /// 3: $(elements) - Wrap jQuery functionality around a single or multiple DOM Element(s). - /// 4: $(callback) - A shorthand for $(document).ready(). - /// - /// - /// 1: expression - An expression to search with. - /// 2: html - A string of HTML to create on the fly. - /// 3: elements - DOM element(s) to be encapsulated by a jQuery object. - /// 4: callback - The function to execute when the DOM is ready. - /// - /// - /// 1: context - A DOM Element, Document or jQuery to use as context. - /// - /// - /// The DOM node context originally passed to jQuery() (if none was passed then context will be equal to the document). - /// - /// - /// A selector representing selector originally passed to jQuery(). - /// - /// - - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); - }, - - // A simple way to check for HTML strings or ID strings - // (both of which we optimize for) - quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, - // Is it a simple selector - isSimple = /^.[^:#\[\.,]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - /// - /// 1: $(expression, context) - This function accepts a string containing a CSS selector which is then used to match a set of elements. - /// 2: $(html) - Create DOM elements on-the-fly from the provided String of raw HTML. - /// 3: $(elements) - Wrap jQuery functionality around a single or multiple DOM Element(s). - /// 4: $(callback) - A shorthand for $(document).ready(). - /// - /// - /// 1: expression - An expression to search with. - /// 2: html - A string of HTML to create on the fly. - /// 3: elements - DOM element(s) to be encapsulated by a jQuery object. - /// 4: callback - The function to execute when the DOM is ready. - /// - /// - /// 1: context - A DOM Element, Document or jQuery to use as context. - /// - /// - - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - this.context = selector; - return this; - } - // Handle HTML strings - if (typeof selector === "string") { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec(selector); - - // Verify a match, and that no context was specified for #id - if (match && (match[1] || !context)) { - - // HANDLE: $(html) -> $(array) - if (match[1]) - selector = jQuery.clean([match[1]], context); - - // HANDLE: $("#id") - else { - var elem = document.getElementById(match[3]); - - // Handle the case where IE and Opera return items - // by name instead of ID - if (elem && elem.id != match[3]) - return jQuery().find(selector); - - // Otherwise, we inject the element directly into the jQuery object - var ret = jQuery(elem || []); - ret.context = document; - ret.selector = selector; - return ret; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return jQuery(context).find(selector); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return jQuery( document ).ready( selector ); - - // Make sure that old selector state is passed along - if ( selector.selector && selector.context ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return this.setArray(jQuery.isArray( selector ) ? - selector : - jQuery.makeArray(selector)); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.3.2", - - // The number of elements contained in the matched element set - size: function() { - /// - /// The number of elements currently matched. - /// Part of Core - /// - /// - - return this.length; - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - /// - /// Access a single matched element. num is used to access the - /// Nth element matched. - /// Part of Core - /// - /// - /// - /// Access the element in the Nth position. - /// - - return num == undefined ? - - // Return a 'clean' array - Array.prototype.slice.call( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - /// - /// Set the jQuery object to an array of elements, while maintaining - /// the stack. - /// Part of Core - /// - /// - /// - /// An array of elements - /// - - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) - ret.selector = this.selector + (this.selector ? " " : "") + selector; - else if ( name ) - ret.selector = this.selector + "." + name + "(" + selector + ")"; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - /// - /// Set the jQuery object to an array of elements. This operation is - /// completely destructive - be sure to use .pushStack() if you wish to maintain - /// the jQuery stack. - /// Part of Core - /// - /// - /// - /// An array of elements - /// - - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - /// - /// Execute a function within the context of every matched element. - /// This means that every time the passed-in function is executed - /// (which is once for every element matched) the 'this' keyword - /// points to the specific element. - /// Additionally, the function, when executed, is passed a single - /// argument representing the position of the element in the matched - /// set. - /// Part of Core - /// - /// - /// - /// A function to execute - /// - - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - /// - /// Searches every matched element for the object and returns - /// the index of the element, if found, starting with zero. - /// Returns -1 if the object wasn't found. - /// Part of Core - /// - /// - /// - /// Object to search for - /// - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem && elem.jquery ? elem[0] : elem - , this ); - }, - - attr: function( name, value, type ) { - /// - /// Set a single property to a computed value, on all matched elements. - /// Instead of a value, a function is provided, that computes the value. - /// Part of DOM/Attributes - /// - /// - /// - /// The name of the property to set. - /// - /// - /// A function returning the value to set. - /// - - var options = name; - - // Look for the case where we're accessing a style value - if ( typeof name === "string" ) - if ( value === undefined ) - return this[0] && jQuery[ type || "attr" ]( this[0], name ); - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - /// - /// Set a single style property to a value, on all matched elements. - /// If a number is provided, it is automatically converted into a pixel value. - /// Part of CSS - /// - /// - /// - /// The name of the property to set. - /// - /// - /// The value to set the property to. - /// - - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - /// - /// Set the text contents of all matched elements. - /// Similar to html(), but escapes HTML (replace "<" and ">" with their - /// HTML entities). - /// Part of DOM/Attributes - /// - /// - /// - /// The text value to set the contents of the element to. - /// - - if ( typeof text !== "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - /// - /// Wrap all matched elements with a structure of other elements. - /// This wrapping process is most useful for injecting additional - /// stucture into a document, without ruining the original semantic - /// qualities of a document. - /// This works by going through the first element - /// provided and finding the deepest ancestor element within its - /// structure - it is that element that will en-wrap everything else. - /// This does not work with elements that contain text. Any necessary text - /// must be added after the wrapping is done. - /// Part of DOM/Manipulation - /// - /// - /// - /// A DOM element that will be wrapped around the target. - /// - - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).clone(); - - if ( this[0].parentNode ) - wrap.insertBefore( this[0] ); - - wrap.map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }).append(this); - } - - return this; - }, - - wrapInner: function( html ) { - /// - /// Wraps the inner child contents of each matched elemenht (including text nodes) with an HTML structure. - /// - /// - /// A string of HTML or a DOM element that will be wrapped around the target contents. - /// - /// - - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - /// - /// Wrap all matched elements with a structure of other elements. - /// This wrapping process is most useful for injecting additional - /// stucture into a document, without ruining the original semantic - /// qualities of a document. - /// This works by going through the first element - /// provided and finding the deepest ancestor element within its - /// structure - it is that element that will en-wrap everything else. - /// This does not work with elements that contain text. Any necessary text - /// must be added after the wrapping is done. - /// Part of DOM/Manipulation - /// - /// - /// - /// A DOM element that will be wrapped around the target. - /// - - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - /// - /// Append content to the inside of every matched element. - /// This operation is similar to doing an appendChild to all the - /// specified elements, adding them into the document. - /// Part of DOM/Manipulation - /// - /// - /// - /// Content to append to the target - /// - - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - /// - /// Prepend content to the inside of every matched element. - /// This operation is the best way to insert elements - /// inside, at the beginning, of all matched elements. - /// Part of DOM/Manipulation - /// - /// - /// - /// Content to prepend to the target. - /// - - return this.domManip(arguments, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - /// - /// Insert content before each of the matched elements. - /// Part of DOM/Manipulation - /// - /// - /// - /// Content to insert before each target. - /// - - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - /// - /// Insert content after each of the matched elements. - /// Part of DOM/Manipulation - /// - /// - /// - /// Content to insert after each target. - /// - - return this.domManip(arguments, false, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - /// - /// End the most recent 'destructive' operation, reverting the list of matched elements - /// back to its previous state. After an end operation, the list of matched elements will - /// revert to the last state of matched elements. - /// If there was no destructive operation before, an empty set is returned. - /// Part of DOM/Traversing - /// - /// - - return this.prevObject || jQuery( [] ); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: [].push, - sort: [].sort, - splice: [].splice, - - find: function( selector ) { - /// - /// Searches for all elements that match the specified expression. - /// This method is a good way to find additional descendant - /// elements with which to process. - /// All searching is done using a jQuery expression. The expression can be - /// written using CSS 1-3 Selector syntax, or basic XPath. - /// Part of DOM/Traversing - /// - /// - /// - /// An expression to search with. - /// - /// - - if ( this.length === 1 ) { - var ret = this.pushStack( [], "find", selector ); - ret.length = 0; - jQuery.find( selector, this[0], ret ); - return ret; - } else { - return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - })), "find", selector ); - } - }, - - clone: function( events ) { - /// - /// Clone matched DOM Elements and select the clones. - /// This is useful for moving copies of the elements to another - /// location in the DOM. - /// Part of DOM/Manipulation - /// - /// - /// - /// (Optional) Set to false if you don't want to clone all descendant nodes, in addition to the element itself. - /// - - // Do the clone - var ret = this.map(function(){ - if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var html = this.outerHTML; - if ( !html ) { - var div = this.ownerDocument.createElement("div"); - div.appendChild( this.cloneNode(true) ); - html = div.innerHTML; - } - - return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; - } else - return this.cloneNode(true); - }); - - // Copy the events from the original to the clone - if ( events === true ) { - var orig = this.find("*").andSelf(), i = 0; - - ret.find("*").andSelf().each(function(){ - if ( this.nodeName !== orig[i].nodeName ) - return; - - var events = jQuery.data( orig[i], "events" ); - - for ( var type in events ) { - for ( var handler in events[ type ] ) { - jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); - } - } - - i++; - }); - } - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - /// - /// Removes all elements from the set of matched elements that do not - /// pass the specified filter. This method is used to narrow down - /// the results of a search. - /// }) - /// Part of DOM/Traversing - /// - /// - /// - /// A function to use for filtering - /// - /// - - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ - return elem.nodeType === 1; - }) ), "filter", selector ); - }, - - closest: function( selector ) { - /// - /// Get a set of elements containing the closest parent element that matches the specified selector, the starting element included. - /// - /// - /// - /// An expression to filter the elements with. - /// - /// - - var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, - closer = 0; - - return this.map(function(){ - var cur = this; - while ( cur && cur.ownerDocument ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { - jQuery.data(cur, "closest", closer); - return cur; - } - cur = cur.parentNode; - closer++; - } - }); - }, - - not: function( selector ) { - /// - /// Removes any elements inside the array of elements from the set - /// of matched elements. This method is used to remove one or more - /// elements from a jQuery object. - /// Part of DOM/Traversing - /// - /// - /// A set of elements to remove from the jQuery set of matched elements. - /// - /// - - if ( typeof selector === "string" ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - /// - /// Adds one or more Elements to the set of matched elements. - /// Part of DOM/Traversing - /// - /// - /// One or more Elements to add - /// - /// - - return this.pushStack( jQuery.unique( jQuery.merge( - this.get(), - typeof selector === "string" ? - jQuery( selector ) : - jQuery.makeArray( selector ) - ))); - }, - - is: function( selector ) { - /// - /// Checks the current selection against an expression and returns true, - /// if at least one element of the selection fits the given expression. - /// Does return false, if no element fits or the expression is not valid. - /// filter(String) is used internally, therefore all rules that apply there - /// apply here, too. - /// Part of DOM/Traversing - /// - /// - /// - /// The expression with which to filter - /// - - return !!selector && jQuery.multiFilter( selector, this ).length > 0; - }, - - hasClass: function( selector ) { - /// - /// Checks the current selection against a class and returns whether at least one selection has a given class. - /// - /// The class to check against - /// True if at least one element in the selection has the class, otherwise false. - - return !!selector && this.is( "." + selector ); - }, - - val: function( value ) { - /// - /// Set the value of every matched element. - /// Part of DOM/Attributes - /// - /// - /// - /// Set the property to the specified value. - /// - - if ( value === undefined ) { - var elem = this[0]; - - if ( elem ) { - if( jQuery.nodeName( elem, 'option' ) ) - return (elem.attributes.value || {}).specified ? elem.value : elem.text; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - } - - // Everything else, we just grab the value - return (elem.value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - if ( typeof value === "number" ) - value += ''; - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = jQuery.makeArray(value); - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - /// - /// Set the html contents of every matched element. - /// This property is not available on XML documents. - /// Part of DOM/Attributes - /// - /// - /// - /// Set the html contents to the specified value. - /// - - return value === undefined ? - (this[0] ? - this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - /// - /// Replaces all matched element with the specified HTML or DOM elements. - /// - /// - /// The content with which to replace the matched elements. - /// - /// The element that was just replaced. - - return this.after( value ).remove(); - }, - - eq: function( i ) { - /// - /// Reduce the set of matched elements to a single element. - /// The position of the element in the set of matched elements - /// starts at 0 and goes to length - 1. - /// Part of Core - /// - /// - /// - /// pos The index of the element that you wish to limit to. - /// - - return this.slice( i, +i + 1 ); - }, - - slice: function() { - /// - /// Selects a subset of the matched elements. Behaves exactly like the built-in Array slice method. - /// - /// Where to start the subset (0-based). - /// Where to end the subset (not including the end element itself). - /// If omitted, ends at the end of the selection - /// The sliced elements - - return this.pushStack( Array.prototype.slice.apply( this, arguments ), - "slice", Array.prototype.slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - /// - /// This member is internal. - /// - /// - /// - - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - /// - /// Adds the previous selection to the current selection. - /// - /// - - return this.add( this.prevObject ); - }, - - domManip: function( args, table, callback ) { - /// - /// Args - /// - /// - /// Insert TBODY in TABLEs if one is not found. - /// - /// - /// If dir<0, process args in reverse order. - /// - /// - /// The function doing the DOM manipulation. - /// - /// - /// - /// Part of Core - /// - - if ( this[0] ) { - var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), - scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), - first = fragment.firstChild; - - if ( first ) - for ( var i = 0, l = this.length; i < l; i++ ) - callback.call( root(this[i], first), this.length > 1 || i > 0 ? - fragment.cloneNode(true) : fragment ); - - if ( scripts ) - jQuery.each( scripts, evalScript ); - } - - return this; - - function root( elem, cur ) { - return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; - } - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -function evalScript( i, elem ) { - /// - /// This method is internal. - /// - /// - - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -function now(){ - /// - /// Gets the current date. - /// - /// The current date. - return +new Date; -} - -jQuery.extend = jQuery.fn.extend = function() { - /// - /// Extend one object with one or more others, returning the original, - /// modified, object. This is a great utility for simple inheritance. - /// jQuery.extend(settings, options); - /// var settings = jQuery.extend({}, defaults, options); - /// Part of JavaScript - /// - /// - /// The object to extend - /// - /// - /// The object that will be merged into the first. - /// - /// - /// (optional) More objects to merge into the first - /// - /// - - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - var src = target[ name ], copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) - continue; - - // Recurse if we're merging object values - if ( deep && copy && typeof copy === "object" && !copy.nodeType ) - target[ name ] = jQuery.extend( deep, - // Never move original objects, clone them - src || ( copy.length != null ? [ ] : { } ) - , copy ); - - // Don't bring in undefined values - else if ( copy !== undefined ) - target[ name ] = copy; - - } - - // Return the modified object - return target; -}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, - // cache defaultView - defaultView = document.defaultView || {}, - toString = Object.prototype.toString; - -jQuery.extend({ - noConflict: function( deep ) { - /// - /// Run this function to give control of the $ variable back - /// to whichever library first implemented it. This helps to make - /// sure that jQuery doesn't conflict with the $ object - /// of other libraries. - /// By using this function, you will only be able to access jQuery - /// using the 'jQuery' variable. For example, where you used to do - /// $("div p"), you now must do jQuery("div p"). - /// Part of Core - /// - /// - - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - /// - /// Determines if the parameter passed is a function. - /// - /// The object to check - /// True if the parameter is a function; otherwise false. - - return toString.call(obj) === "[object Function]"; - }, - - isArray: function(obj) { - /// - /// Determine if the parameter passed is an array. - /// - /// Object to test whether or not it is an array. - /// True if the parameter is a function; otherwise false. - - return toString.call(obj) === "[object Array]"; - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - /// - /// Determines if the parameter passed is an XML document. - /// - /// The object to test - /// True if the parameter is an XML document; otherwise false. - - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && jQuery.isXMLDoc(elem.ownerDocument); - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - /// - /// Internally evaluates a script in a global context. - /// - /// - - if ( data && /\S/.test(data) ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) - script.appendChild( document.createTextNode( data ) ); - else - script.text = data; - - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709). - head.insertBefore( script, head.firstChild ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - /// - /// Checks whether the specified element has the specified DOM node name. - /// - /// The element to examine - /// The node name to check - /// True if the specified node name matches the node's DOM node name; otherwise false - - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - /// - /// A generic iterator function, which can be used to seemlessly - /// iterate over both objects and arrays. This function is not the same - /// as $().each() - which is used to iterate, exclusively, over a jQuery - /// object. This function can be used to iterate over anything. - /// The callback has two arguments:the key (objects) or index (arrays) as first - /// the first, and the value as the second. - /// Part of JavaScript - /// - /// - /// The object, or array, to iterate over. - /// - /// - /// The function that will be executed on every object. - /// - /// - - var name, i = 0, length = object.length; - - if ( args ) { - if ( length === undefined ) { - for ( name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( ; i < length; ) - if ( callback.apply( object[ i++ ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( length === undefined ) { - for ( name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - /// - /// This method is internal. - /// - /// - // This member is not documented within the jQuery API: http://docs.jquery.com/action/edit/Internals/jQuery.prop - - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - /// - /// Internal use only; use addClass('class') - /// - /// - - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - /// - /// Internal use only; use removeClass('class') - /// - /// - - if (elem.nodeType == 1) - elem.className = classNames !== undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use hasClass("class") - has: function( elem, className ) { - /// - /// Internal use only; use hasClass('class') - /// - /// - - return elem && jQuery.inArray(className, (elem.className || elem).toString().split(/\s+/)) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - /// - /// Swap in/out style options. - /// - - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force, extra ) { - /// - /// This method is internal only. - /// - /// - // This method is undocumented in jQuery API: http://docs.jquery.com/action/edit/Internals/jQuery.css - - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - - if ( extra === "border" ) - return; - - jQuery.each( which, function() { - if ( !extra ) - val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - if ( extra === "margin" ) - val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; - else - val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - } - - if ( elem.offsetWidth !== 0 ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, Math.round(val)); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - /// - /// This method is internal only. - /// - /// - // This method is undocumented in jQuery API: http://docs.jquery.com/action/edit/Internals/jQuery.curCSS - - var ret, style = elem.style; - - // We need to handle opacity special in IE - if ( name == "opacity" && !jQuery.support.opacity ) { - ret = jQuery.attr( style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && style && style[ name ] ) - ret = style[ name ]; - - else if ( defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var computedStyle = defaultView.getComputedStyle( elem, null ); - - if ( computedStyle ) - ret = computedStyle.getPropertyValue( name ); - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; - } - } - - return ret; - }, - - clean: function( elems, context, fragment ) { - /// - /// This method is internal only. - /// - /// - // This method is undocumented in the jQuery API: http://docs.jquery.com/action/edit/Internals/jQuery.clean - - - context = context || document; - - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { - var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); - if ( match ) - return [ context.createElement( match[1] ) ]; - } - - var ret = [], scripts = [], div = context.createElement("div"); - - jQuery.each(elems, function(i, elem){ - if ( typeof elem === "number" ) - elem += ''; - - if ( !elem ) - return; - - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
" ] || - - !tags.indexOf("", "" ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and