f-spot r4127 - in trunk/dpap-sharp: . launcher lib
- From: apart svn gnome org
- To: svn-commits-list gnome org
- Subject: f-spot r4127 - in trunk/dpap-sharp: . launcher lib
- Date: Tue, 1 Jul 2008 17:02:33 +0000 (UTC)
Author: apart
Date: Tue Jul 1 17:02:33 2008
New Revision: 4127
URL: http://svn.gnome.org/viewvc/f-spot?rev=4127&view=rev
Log:
Initial import of the GSoC 2008 project
Added:
trunk/dpap-sharp/
trunk/dpap-sharp/launcher/
trunk/dpap-sharp/launcher/AssemblyInfo.cs
trunk/dpap-sharp/launcher/Main.cs
trunk/dpap-sharp/launcher/dpap-launcher.mdp
trunk/dpap-sharp/launcher/dpap-launcher.pidb (contents, props changed)
trunk/dpap-sharp/launcher/main.c
trunk/dpap-sharp/lib/
trunk/dpap-sharp/lib/AssemblyInfo.cs
trunk/dpap-sharp/lib/AuthenticationException.cs
trunk/dpap-sharp/lib/BrokenMD5.cs
trunk/dpap-sharp/lib/Client.cs
trunk/dpap-sharp/lib/ContentCodeBag.cs
trunk/dpap-sharp/lib/ContentFetcher.cs
trunk/dpap-sharp/lib/ContentParser.cs
trunk/dpap-sharp/lib/ContentWriter.cs
trunk/dpap-sharp/lib/Database.cs
trunk/dpap-sharp/lib/Discovery.cs
trunk/dpap-sharp/lib/Hasher.cs
trunk/dpap-sharp/lib/LoginException.cs
trunk/dpap-sharp/lib/MyClass.cs
trunk/dpap-sharp/lib/Playlist.cs
trunk/dpap-sharp/lib/Server.cs
trunk/dpap-sharp/lib/ServerInfo.cs
trunk/dpap-sharp/lib/Source.cs
trunk/dpap-sharp/lib/Track.cs
trunk/dpap-sharp/lib/User.cs
trunk/dpap-sharp/lib/Utility.cs
trunk/dpap-sharp/lib/content-codes (contents, props changed)
trunk/dpap-sharp/lib/dpap-sharp.mdp
trunk/dpap-sharp/lib/dpap-sharp.pidb (contents, props changed)
Added: trunk/dpap-sharp/launcher/AssemblyInfo.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/launcher/AssemblyInfo.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,30 @@
+// AssemblyInfo.cs created with MonoDevelop
+// User: andrzej at 10:26Â2008-06-01
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("dpap-launcher")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// If the build and revision are set to '*' they will be updated automatically.
+
+[assembly: AssemblyVersion("1.0.*.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+[assembly: AssemblyDelaySign(false)]
+[assembly: AssemblyKeyFile("")]
Added: trunk/dpap-sharp/launcher/Main.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/launcher/Main.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,69 @@
+// Main.cs created with MonoDevelop
+// User: andrzej at 10:26Â2008-06-01
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Collections;
+using Mono.Unix;
+//using Gtk;
+
+using DPAP;
+
+
+
+namespace DPAP {
+
+ class MainClass
+ {
+
+ public static void Main(string[] args)
+ {
+
+ ServiceDiscovery sd = new ServiceDiscovery();
+ sd.Found += OnServiceFound;
+ sd.Start();
+
+
+// sd.Services[0];
+ Console.ReadLine();
+ }
+ private static void OnServiceFound(object o, ServiceArgs args)
+ {
+ Service service = args.Service;
+ Client client;
+// ThreadAssist.Spawn(delegate {
+ // try {
+
+ System.Console.WriteLine("Connecting to {0} at {1}:{2}", service.Name, service.Address, service.Port);
+ client = new Client( service );
+ Console.ReadLine();
+ // });
+ /* //client.Updated += OnClientUpdated;
+ if(client.AuthenticationMethod == AuthenticationMethod.None) {
+ client.Login();
+
+ }
+ */
+ //} else {
+ // ThreadAssist.ProxyToMain(PromptLogin);
+ // }
+ /* } catch(Exception e) {
+ ThreadAssist.ProxyToMain(delegate {
+ DaapErrorView error_view = new DaapErrorView(this, DaapErrorType.BrokenAuthentication);
+ while(box.Children.Length > 0) {
+ box.Remove(box.Children[0]);
+ }
+ box.PackStart(error_view, true, true, 0);
+ error_view.Show();
+ });
+ }*/
+
+ // is_activating = false;
+ // });
+
+ }
+ }
+}
Added: trunk/dpap-sharp/launcher/dpap-launcher.mdp
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/launcher/dpap-launcher.mdp Tue Jul 1 17:02:33 2008
@@ -0,0 +1,28 @@
+<Project name="dpap-launcher" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
+ <Configurations active="Debug">
+ <Configuration name="Debug" ctype="DotNetProjectConfiguration">
+ <Output directory="bin/Debug" assembly="dpap-launcher" />
+ <Build debugmode="True" target="Exe" />
+ <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+ <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" definesymbols="DEBUG" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+ </Configuration>
+ <Configuration name="Release" ctype="DotNetProjectConfiguration">
+ <Output directory="bin/Release" assembly="dpap-launcher" />
+ <Build debugmode="False" target="Exe" />
+ <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
+ <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+ </Configuration>
+ </Configurations>
+ <Contents>
+ <File name="Main.cs" subtype="Code" buildaction="Compile" />
+ <File name="AssemblyInfo.cs" subtype="Code" buildaction="Compile" />
+ </Contents>
+ <References>
+ <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+ <ProjectReference type="Gac" localcopy="True" refto="Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+ <ProjectReference type="Gac" localcopy="True" refto="Banshee.Base, Version=0.13.2.20643, Culture=neutral" />
+ <ProjectReference type="Gac" localcopy="True" refto="Banshee.Core, Version=1.0.0.38867, Culture=neutral" />
+ <ProjectReference type="Gac" localcopy="True" refto="Banshee.Services, Version=1.0.0.38868, Culture=neutral" />
+ <ProjectReference type="Project" localcopy="True" refto="dpap-sharp" />
+ </References>
+</Project>
\ No newline at end of file
Added: trunk/dpap-sharp/launcher/dpap-launcher.pidb
==============================================================================
Binary file. No diff available.
Added: trunk/dpap-sharp/launcher/main.c
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/launcher/main.c Tue Jul 1 17:02:33 2008
@@ -0,0 +1,9 @@
+// project created on 2008-06-01 at 10:25
+#include <stdio.h>
+
+int main (int argc, char *argv[])
+{
+ printf ("Hello world!\n");
+
+ return 0;
+}
Added: trunk/dpap-sharp/lib/AssemblyInfo.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/AssemblyInfo.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,30 @@
+// AssemblyInfo.cs created with MonoDevelop
+// User: andrzej at 12:50Â2008-04-23
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("dpap-sharp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// If the build and revision are set to '*' they will be updated automatically.
+
+[assembly: AssemblyVersion("1.0.*.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+[assembly: AssemblyDelaySign(false)]
+[assembly: AssemblyKeyFile("")]
Added: trunk/dpap-sharp/lib/AuthenticationException.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/AuthenticationException.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,11 @@
+
+using System;
+
+namespace DPAP {
+
+ public class AuthenticationException : ApplicationException {
+
+ public AuthenticationException (string msg) : base (msg) {
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/BrokenMD5.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/BrokenMD5.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,519 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+//
+// BrokenMD5 Class implementation
+//
+// Authors:
+// Matthew S. Ford (Matthew S Ford Rose-Hulman Edu)
+// Sebastien Pouliot (sebastien ximian com)
+// Jon Lech Johansen (jon nanocrew net)
+//
+// Copyright 2001 by Matthew S. Ford.
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// 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.
+//
+
+using System;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+
+namespace DPAP {
+
+ internal class BrokenMD5 : MD5 {
+ private const int BLOCK_SIZE_BYTES = 64;
+ private const int HASH_SIZE_BYTES = 16;
+ private uint[] _H;
+ private uint[] buff;
+ private ulong count;
+ private byte[] _ProcessingBuffer; // Used to start data when passed less than a block worth.
+ private int _ProcessingBufferCount; // Counts how much data we have stored that still needs processed.
+ private int _version;
+
+ public BrokenMD5 ( int version )
+ {
+ _H = new uint[4];
+ buff = new uint[16];
+ _ProcessingBuffer = new byte [BLOCK_SIZE_BYTES];
+ _version = version;
+
+ Initialize();
+ }
+
+ ~BrokenMD5 ()
+ {
+ Dispose (false);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ if (_ProcessingBuffer != null) {
+ Array.Clear (_ProcessingBuffer, 0, _ProcessingBuffer.Length);
+ _ProcessingBuffer = null;
+ }
+ if (_H != null) {
+ Array.Clear (_H, 0, _H.Length);
+ _H = null;
+ }
+ if (buff != null) {
+ Array.Clear (buff, 0, buff.Length);
+ buff = null;
+ }
+ }
+
+ protected override void HashCore (byte[] rgb, int start, int size)
+ {
+ int i;
+ State = 1;
+
+ if (_ProcessingBufferCount != 0) {
+ if (size < (BLOCK_SIZE_BYTES - _ProcessingBufferCount)) {
+ System.Buffer.BlockCopy (rgb, start, _ProcessingBuffer, _ProcessingBufferCount, size);
+ _ProcessingBufferCount += size;
+ return;
+ }
+ else {
+ i = (BLOCK_SIZE_BYTES - _ProcessingBufferCount);
+ System.Buffer.BlockCopy (rgb, start, _ProcessingBuffer, _ProcessingBufferCount, i);
+ ProcessBlock (_ProcessingBuffer, 0);
+ _ProcessingBufferCount = 0;
+ start += i;
+ size -= i;
+ }
+ }
+
+ for (i=0; i<size-size%BLOCK_SIZE_BYTES; i += BLOCK_SIZE_BYTES) {
+ ProcessBlock (rgb, start+i);
+ }
+
+ if (size%BLOCK_SIZE_BYTES != 0) {
+ System.Buffer.BlockCopy (rgb, size-size%BLOCK_SIZE_BYTES+start, _ProcessingBuffer, 0, size%BLOCK_SIZE_BYTES);
+ _ProcessingBufferCount = size%BLOCK_SIZE_BYTES;
+ }
+ }
+
+ protected override byte[] HashFinal ()
+ {
+ byte[] hash = new byte[16];
+ int i, j;
+
+ ProcessFinalBlock (_ProcessingBuffer, 0, _ProcessingBufferCount);
+
+ for (i=0; i<4; i++) {
+ for (j=0; j<4; j++) {
+ hash[i*4+j] = (byte)(_H[i] >> j*8);
+ }
+ }
+
+ return hash;
+ }
+
+ public override void Initialize ()
+ {
+ count = 0;
+ _ProcessingBufferCount = 0;
+
+ _H[0] = 0x67452301;
+ _H[1] = 0xefcdab89;
+ _H[2] = 0x98badcfe;
+ _H[3] = 0x10325476;
+ }
+
+ private void ProcessBlock (byte[] inputBuffer, int inputOffset)
+ {
+ uint a, b, c, d;
+ int i;
+
+ count += BLOCK_SIZE_BYTES;
+
+ for (i=0; i<16; i++) {
+ buff[i] = (uint)(inputBuffer[inputOffset+4*i])
+ | (((uint)(inputBuffer[inputOffset+4*i+1])) << 8)
+ | (((uint)(inputBuffer[inputOffset+4*i+2])) << 16)
+ | (((uint)(inputBuffer[inputOffset+4*i+3])) << 24);
+ }
+
+ a = _H[0];
+ b = _H[1];
+ c = _H[2];
+ d = _H[3];
+
+ // This function was unrolled because it seems to be doubling our performance with current compiler/VM.
+ // Possibly roll up if this changes.
+
+ // ---- Round 1 --------
+
+ a += (((c ^ d) & b) ^ d) + (uint) K [0] + buff [0];
+ a = (a << 7) | (a >> 25);
+ a += b;
+
+ d += (((b ^ c) & a) ^ c) + (uint) K [1] + buff [1];
+ d = (d << 12) | (d >> 20);
+ d += a;
+
+ c += (((a ^ b) & d) ^ b) + (uint) K [2] + buff [2];
+ c = (c << 17) | (c >> 15);
+ c += d;
+
+ b += (((d ^ a) & c) ^ a) + (uint) K [3] + buff [3];
+ b = (b << 22) | (b >> 10);
+ b += c;
+
+ a += (((c ^ d) & b) ^ d) + (uint) K [4] + buff [4];
+ a = (a << 7) | (a >> 25);
+ a += b;
+
+ d += (((b ^ c) & a) ^ c) + (uint) K [5] + buff [5];
+ d = (d << 12) | (d >> 20);
+ d += a;
+
+ c += (((a ^ b) & d) ^ b) + (uint) K [6] + buff [6];
+ c = (c << 17) | (c >> 15);
+ c += d;
+
+ b += (((d ^ a) & c) ^ a) + (uint) K [7] + buff [7];
+ b = (b << 22) | (b >> 10);
+ b += c;
+
+ a += (((c ^ d) & b) ^ d) + (uint) K [8] + buff [8];
+ a = (a << 7) | (a >> 25);
+ a += b;
+
+ d += (((b ^ c) & a) ^ c) + (uint) K [9] + buff [9];
+ d = (d << 12) | (d >> 20);
+ d += a;
+
+ c += (((a ^ b) & d) ^ b) + (uint) K [10] + buff [10];
+ c = (c << 17) | (c >> 15);
+ c += d;
+
+ b += (((d ^ a) & c) ^ a) + (uint) K [11] + buff [11];
+ b = (b << 22) | (b >> 10);
+ b += c;
+
+ a += (((c ^ d) & b) ^ d) + (uint) K [12] + buff [12];
+ a = (a << 7) | (a >> 25);
+ a += b;
+
+ d += (((b ^ c) & a) ^ c) + (uint) K [13] + buff [13];
+ d = (d << 12) | (d >> 20);
+ d += a;
+
+ c += (((a ^ b) & d) ^ b) + (uint) K [14] + buff [14];
+ c = (c << 17) | (c >> 15);
+ c += d;
+
+ b += (((d ^ a) & c) ^ a) + (uint) K [15] + buff [15];
+ b = (b << 22) | (b >> 10);
+ b += c;
+
+
+ // ---- Round 2 --------
+
+ a += (((b ^ c) & d) ^ c) + (uint) K [16] + buff [1];
+ a = (a << 5) | (a >> 27);
+ a += b;
+
+ d += (((a ^ b) & c) ^ b) + (uint) K [17] + buff [6];
+ d = (d << 9) | (d >> 23);
+ d += a;
+
+ c += (((d ^ a) & b) ^ a) + (uint) K [18] + buff [11];
+ c = (c << 14) | (c >> 18);
+ c += d;
+
+ b += (((c ^ d) & a) ^ d) + (uint) K [19] + buff [0];
+ b = (b << 20) | (b >> 12);
+ b += c;
+
+ a += (((b ^ c) & d) ^ c) + (uint) K [20] + buff [5];
+ a = (a << 5) | (a >> 27);
+ a += b;
+
+ d += (((a ^ b) & c) ^ b) + (uint) K [21] + buff [10];
+ d = (d << 9) | (d >> 23);
+ d += a;
+
+ c += (((d ^ a) & b) ^ a) + (uint) K [22] + buff [15];
+ c = (c << 14) | (c >> 18);
+ c += d;
+
+ b += (((c ^ d) & a) ^ d) + (uint) K [23] + buff [4];
+ b = (b << 20) | (b >> 12);
+ b += c;
+
+ a += (((b ^ c) & d) ^ c) + (uint) K [24] + buff [9];
+ a = (a << 5) | (a >> 27);
+ a += b;
+
+ d += (((a ^ b) & c) ^ b) + (uint) K [25] + buff [14];
+ d = (d << 9) | (d >> 23);
+ d += a;
+
+ c += (((d ^ a) & b) ^ a) + (uint) K [26] + buff [3];
+ c = (c << 14) | (c >> 18);
+ c += d;
+
+ if( _version == 1 )
+ {
+ b += (((c ^ d) & a) ^ d) + (uint) 0x445a14ed + buff [8];
+ b = (b << 20) | (b >> 12);
+ b += c;
+ }
+ else
+ {
+ b += (((c ^ d) & a) ^ d) + (uint) K [27] + buff [8];
+ b = (b << 20) | (b >> 12);
+ b += c;
+ }
+
+ a += (((b ^ c) & d) ^ c) + (uint) K [28] + buff [13];
+ a = (a << 5) | (a >> 27);
+ a += b;
+
+ d += (((a ^ b) & c) ^ b) + (uint) K [29] + buff [2];
+ d = (d << 9) | (d >> 23);
+ d += a;
+
+ c += (((d ^ a) & b) ^ a) + (uint) K [30] + buff [7];
+ c = (c << 14) | (c >> 18);
+ c += d;
+
+ b += (((c ^ d) & a) ^ d) + (uint) K [31] + buff [12];
+ b = (b << 20) | (b >> 12);
+ b += c;
+
+
+ // ---- Round 3 --------
+
+ a += (b ^ c ^ d) + (uint) K [32] + buff [5];
+ a = (a << 4) | (a >> 28);
+ a += b;
+
+ d += (a ^ b ^ c) + (uint) K [33] + buff [8];
+ d = (d << 11) | (d >> 21);
+ d += a;
+
+ c += (d ^ a ^ b) + (uint) K [34] + buff [11];
+ c = (c << 16) | (c >> 16);
+ c += d;
+
+ b += (c ^ d ^ a) + (uint) K [35] + buff [14];
+ b = (b << 23) | (b >> 9);
+ b += c;
+
+ a += (b ^ c ^ d) + (uint) K [36] + buff [1];
+ a = (a << 4) | (a >> 28);
+ a += b;
+
+ d += (a ^ b ^ c) + (uint) K [37] + buff [4];
+ d = (d << 11) | (d >> 21);
+ d += a;
+
+ c += (d ^ a ^ b) + (uint) K [38] + buff [7];
+ c = (c << 16) | (c >> 16);
+ c += d;
+
+ b += (c ^ d ^ a) + (uint) K [39] + buff [10];
+ b = (b << 23) | (b >> 9);
+ b += c;
+
+ a += (b ^ c ^ d) + (uint) K [40] + buff [13];
+ a = (a << 4) | (a >> 28);
+ a += b;
+
+ d += (a ^ b ^ c) + (uint) K [41] + buff [0];
+ d = (d << 11) | (d >> 21);
+ d += a;
+
+ c += (d ^ a ^ b) + (uint) K [42] + buff [3];
+ c = (c << 16) | (c >> 16);
+ c += d;
+
+ b += (c ^ d ^ a) + (uint) K [43] + buff [6];
+ b = (b << 23) | (b >> 9);
+ b += c;
+
+ a += (b ^ c ^ d) + (uint) K [44] + buff [9];
+ a = (a << 4) | (a >> 28);
+ a += b;
+
+ d += (a ^ b ^ c) + (uint) K [45] + buff [12];
+ d = (d << 11) | (d >> 21);
+ d += a;
+
+ c += (d ^ a ^ b) + (uint) K [46] + buff [15];
+ c = (c << 16) | (c >> 16);
+ c += d;
+
+ b += (c ^ d ^ a) + (uint) K [47] + buff [2];
+ b = (b << 23) | (b >> 9);
+ b += c;
+
+
+ // ---- Round 4 --------
+
+ a += (((~d) | b) ^ c) + (uint) K [48] + buff [0];
+ a = (a << 6) | (a >> 26);
+ a += b;
+
+ d += (((~c) | a) ^ b) + (uint) K [49] + buff [7];
+ d = (d << 10) | (d >> 22);
+ d += a;
+
+ c += (((~b) | d) ^ a) + (uint) K [50] + buff [14];
+ c = (c << 15) | (c >> 17);
+ c += d;
+
+ b += (((~a) | c) ^ d) + (uint) K [51] + buff [5];
+ b = (b << 21) | (b >> 11);
+ b += c;
+
+ a += (((~d) | b) ^ c) + (uint) K [52] + buff [12];
+ a = (a << 6) | (a >> 26);
+ a += b;
+
+ d += (((~c) | a) ^ b) + (uint) K [53] + buff [3];
+ d = (d << 10) | (d >> 22);
+ d += a;
+
+ c += (((~b) | d) ^ a) + (uint) K [54] + buff [10];
+ c = (c << 15) | (c >> 17);
+ c += d;
+
+ b += (((~a) | c) ^ d) + (uint) K [55] + buff [1];
+ b = (b << 21) | (b >> 11);
+ b += c;
+
+ a += (((~d) | b) ^ c) + (uint) K [56] + buff [8];
+ a = (a << 6) | (a >> 26);
+ a += b;
+
+ d += (((~c) | a) ^ b) + (uint) K [57] + buff [15];
+ d = (d << 10) | (d >> 22);
+ d += a;
+
+ c += (((~b) | d) ^ a) + (uint) K [58] + buff [6];
+ c = (c << 15) | (c >> 17);
+ c += d;
+
+ b += (((~a) | c) ^ d) + (uint) K [59] + buff [13];
+ b = (b << 21) | (b >> 11);
+ b += c;
+
+ a += (((~d) | b) ^ c) + (uint) K [60] + buff [4];
+ a = (a << 6) | (a >> 26);
+ a += b;
+
+ d += (((~c) | a) ^ b) + (uint) K [61] + buff [11];
+ d = (d << 10) | (d >> 22);
+ d += a;
+
+ c += (((~b) | d) ^ a) + (uint) K [62] + buff [2];
+ c = (c << 15) | (c >> 17);
+ c += d;
+
+ b += (((~a) | c) ^ d) + (uint) K [63] + buff [9];
+ b = (b << 21) | (b >> 11);
+ b += c;
+
+ _H [0] += a;
+ _H [1] += b;
+ _H [2] += c;
+ _H [3] += d;
+ }
+
+ private void ProcessFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ ulong total = count + (ulong)inputCount;
+ int paddingSize = (int)(56 - total % BLOCK_SIZE_BYTES);
+
+ if (paddingSize < 1)
+ paddingSize += BLOCK_SIZE_BYTES;
+
+ byte[] fooBuffer = new byte [inputCount+paddingSize+8];
+
+ for (int i=0; i<inputCount; i++) {
+ fooBuffer[i] = inputBuffer[i+inputOffset];
+ }
+
+ fooBuffer[inputCount] = 0x80;
+ for (int i=inputCount+1; i<inputCount+paddingSize; i++) {
+ fooBuffer[i] = 0x00;
+ }
+
+ // I deal in bytes. The algorithm deals in bits.
+ ulong size = total << 3;
+ AddLength (size, fooBuffer, inputCount+paddingSize);
+ ProcessBlock (fooBuffer, 0);
+
+ if (inputCount+paddingSize+8 == 128) {
+ ProcessBlock(fooBuffer, 64);
+ }
+ }
+
+ internal void AddLength (ulong length, byte[] buffer, int position)
+ {
+ buffer [position++] = (byte)(length);
+ buffer [position++] = (byte)(length >> 8);
+ buffer [position++] = (byte)(length >> 16);
+ buffer [position++] = (byte)(length >> 24);
+ buffer [position++] = (byte)(length >> 32);
+ buffer [position++] = (byte)(length >> 40);
+ buffer [position++] = (byte)(length >> 48);
+ buffer [position] = (byte)(length >> 56);
+ }
+
+ private readonly static uint[] K = {
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+ 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+ 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+ 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+ };
+ }
+}
Added: trunk/dpap-sharp/lib/Client.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Client.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,231 @@
+// Client.cs created with MonoDevelop
+// User: andrzej at 11:19Â2008-06-12
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.IO;
+using System.Web;
+using System.Net;
+using System.Text;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace DPAP
+{
+
+
+ public class Client
+ {
+
+ private const int UpdateSleepInterval = 2 * 60 * 1000; // 2 minutes
+
+ private IPAddress address;
+ private UInt16 port;
+ private ContentCodeBag bag;
+ private ServerInfo serverInfo;
+ private List<Database> databases = new List<Database> ();
+ private ContentFetcher fetcher;
+ private int revision;
+ private bool updateRunning;
+
+ public event EventHandler Updated;
+
+ internal int Revision {
+ get { return revision; }
+ }
+
+ public string Name {
+ get { return serverInfo.Name; }
+ }
+
+ public IPAddress Address {
+ get { return address; }
+ }
+
+ public ushort Port {
+ get { return port; }
+ }
+
+ public AuthenticationMethod AuthenticationMethod {
+ get { return serverInfo.AuthenticationMethod; }
+ }
+
+ public IList<Database> Databases {
+ get { return new ReadOnlyCollection<Database> (databases); }
+ }
+
+ internal ContentCodeBag Bag {
+ get { return bag; }
+ }
+
+ internal ContentFetcher Fetcher {
+ get { return fetcher; }
+ }
+
+ public Client (Service service) : this (service.Address, service.Port) {
+ }
+
+ public Client (string host, UInt16 port) : this (Dns.GetHostEntry (host).AddressList[0], port) {
+ }
+
+ public Client (IPAddress address, UInt16 port) {
+ this.address = address;
+ this.port = port;
+
+ fetcher = new ContentFetcher (address, port);
+ Login(null,null);
+ //byte[] resp= fetcher.Fetch("/server-info");
+ //resp= fetcher.Fetch("/login");
+ // ContentNode node = ContentParser.Parse (ContentCodeBag.Default, fetcher.Fetch ("/server-info"));
+ // Console.WriteLine("Odczytalem {0}, {1}", node.Name, node.Value);
+ // serverInfo = ServerInfo.FromNode (node);
+ }
+
+ ~Client () {
+ Dispose ();
+ }
+
+ public void Dispose () {
+ updateRunning = false;
+
+ if (fetcher != null) {
+ fetcher.Dispose ();
+ fetcher = null;
+ }
+ }
+
+ private void ParseSessionId (ContentNode node) {
+ fetcher.SessionId = (int) node.GetChild ("dmap.sessionid").Value;
+ }
+
+ public void Login () {
+ Login (null, null);
+ }
+
+ public void Login (string password) {
+ Login (null, password);
+ }
+
+ public void Login (string username, string password) {
+ fetcher.Username = username;
+ fetcher.Password = password;
+
+ try {
+ bag = ContentCodeBag.ParseCodes (fetcher.Fetch ("/content-codes"));
+ Console.WriteLine("DEBUG LOGIN !");
+ ContentNode node = ContentParser.Parse (bag, fetcher.Fetch ("/login"));
+ ParseSessionId (node);
+ byte[] db_reply = fetcher.Fetch ("/databases");
+
+ Console.Write(BitConverter.ToString(db_reply));
+ //ContentNode dbnode = ContentParser.Parse (bag, fetcher.Fetch ("/databases"));
+ // FetchDatabases ();
+ // Refresh ();
+
+ /* if (serverInfo.SupportsUpdate) {
+ updateRunning = true;
+ Thread thread = new Thread (UpdateLoop);
+ thread.IsBackground = true;
+ thread.Start ();
+ }*/
+ } catch (WebException e) {
+ if (e.Response != null && (e.Response as HttpWebResponse).StatusCode == HttpStatusCode.Unauthorized)
+ throw new AuthenticationException ("Username or password incorrect");
+ else
+ throw new LoginException ("Failed to login", e);
+ } catch (Exception e) {
+ throw new LoginException ("Failed to login", e);
+ }
+ }
+
+ public void Logout () {
+ try {
+ updateRunning = false;
+ fetcher.KillAll ();
+ fetcher.Fetch ("/logout");
+ } catch (WebException) {
+ // some servers don't implement this, etc.
+ }
+
+ fetcher.SessionId = 0;
+ }
+
+ private void FetchDatabases () {
+ ContentNode dbnode = ContentParser.Parse (bag, fetcher.Fetch ("/databases"));
+
+ foreach (ContentNode child in (ContentNode[]) dbnode.Value) {
+ if (child.Name != "dmap.listing")
+ continue;
+
+ foreach (ContentNode item in (ContentNode[]) child.Value) {
+ Database db = new Database (this, item);
+ databases.Add (db);
+ }
+ }
+ }
+
+ private int GetCurrentRevision () {
+ ContentNode revNode = ContentParser.Parse (bag, fetcher.Fetch ("/update"), "dmap.serverrevision");
+ return (int) revNode.Value;
+ }
+
+ private int WaitForRevision (int currentRevision) {
+ ContentNode revNode = ContentParser.Parse (bag, fetcher.Fetch ("/update",
+ "revision-number=" + currentRevision));
+
+ return (int) revNode.GetChild ("dmap.serverrevision").Value;
+ }
+
+ private void Refresh () {
+ int newrev = revision;
+
+ if (serverInfo.SupportsUpdate) {
+ if (revision == 0)
+ newrev = GetCurrentRevision ();
+ else
+ newrev = WaitForRevision (revision);
+
+ if (newrev == revision)
+ return;
+ }
+
+ // Console.WriteLine ("Got new revision: " + newrev);
+ foreach (Database db in databases) {
+ db.Refresh (newrev);
+ }
+
+ revision = newrev;
+ if (Updated != null)
+ Updated (this, new EventArgs ());
+ }
+
+ private void UpdateLoop () {
+ while (true) {
+ try {
+ if (!updateRunning)
+ break;
+
+ Refresh ();
+ } catch (WebException) {
+ if (!updateRunning)
+ break;
+
+ // chill out for a while, maybe the server went down
+ // temporarily or something.
+ Thread.Sleep (UpdateSleepInterval);
+ } catch (Exception e) {
+ if (!updateRunning)
+ break;
+
+ Console.Error.WriteLine ("Exception in update loop: " + e);
+ Thread.Sleep (UpdateSleepInterval);
+ }
+ }
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/ContentCodeBag.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/ContentCodeBag.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,186 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Reflection;
+using System.IO;
+using System.Collections;
+using System.Text;
+using System.Net;
+
+namespace DPAP {
+
+ internal enum ContentType : short {
+ Char = 1,
+ SignedLong = 2,
+ Short = 3,
+ Long = 5,
+ LongLong = 7,
+ String = 9,
+ Date = 10,
+ Version = 11,
+ Container = 12
+ }
+
+ internal struct ContentCode {
+ public int Number;
+ public string Name;
+ public ContentType Type;
+
+ public static ContentCode Zero = new ContentCode ();
+ }
+
+ internal class ContentCodeBag {
+
+ private const int ChunkLength = 8192;
+
+ private static ContentCodeBag defaultBag;
+ private Hashtable codes = new Hashtable ();
+
+ public static ContentCodeBag Default {
+ get {
+ if (defaultBag == null) {
+
+ // this is crappy
+ foreach (string name in Assembly.GetExecutingAssembly().GetManifestResourceNames()) {
+ using (BinaryReader reader = new BinaryReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(name))) {
+ MemoryStream buf = new MemoryStream();
+ byte[] bytes = null;
+
+ do {
+ bytes = reader.ReadBytes(ChunkLength);
+ buf.Write(bytes, 0, bytes.Length);
+ } while (bytes.Length == ChunkLength);
+
+ defaultBag = ContentCodeBag.ParseCodes(buf.GetBuffer());
+ }
+ }
+ }
+
+ return defaultBag;
+ }
+ }
+
+ private ContentCodeBag () {
+ }
+
+ public ContentCode Lookup (int number) {
+ if (codes.ContainsKey (number))
+ return (ContentCode) codes[number];
+ else
+ return ContentCode.Zero;
+ }
+
+ public ContentCode Lookup (string name) {
+ foreach (ContentCode code in codes.Values) {
+ if (code.Name == name)
+ return code;
+ }
+
+ return ContentCode.Zero;
+ }
+
+ private static int GetIntFormat (string code) {
+ return IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (Encoding.ASCII.GetBytes (code), 0));
+ }
+
+ internal static string GetStringFormat (int code) {
+ return Encoding.ASCII.GetString (BitConverter.GetBytes (IPAddress.HostToNetworkOrder (code)));
+ }
+
+ private void AddCode (string num, string name, ContentType type) {
+// Console.WriteLine("Entering ContentCodeBag AddCode(string,string,ContentType)");
+ ContentCode code = new ContentCode ();
+ code.Number = GetIntFormat (num);
+ code.Name = name;
+ code.Type = type;
+ Console.Write(name + ' ');
+
+ codes[code.Number] = code;
+ // Console.WriteLine("Leaving ContentCodeBag AddCode(string,string,ContentType)");
+ }
+
+ internal ContentNode ToNode () {
+ Console.WriteLine("Entering ContentCodeBag ToNode()");
+ ArrayList nodes = new ArrayList ();
+
+ foreach (int number in codes.Keys) {
+ ContentCode code = (ContentCode) codes[number];
+
+ ArrayList contents = new ArrayList ();
+ contents.Add (new ContentNode ("dmap.contentcodesnumber", code.Number));
+ contents.Add (new ContentNode ("dmap.contentcodesname", code.Name));
+ contents.Add (new ContentNode ("dmap.contentcodestype", code.Type));
+
+ ContentNode dict = new ContentNode ("dmap.dictionary", contents);
+ nodes.Add (dict);
+ }
+
+ ContentNode status = new ContentNode ("dmap.status", 200);
+ Console.WriteLine("Leaving ContentCodeBag ToNode()");
+ return new ContentNode ("dmap.contentcodesresponse", status, nodes);
+ }
+
+ public static ContentCodeBag ParseCodes (byte[] buffer) {
+ Console.WriteLine("Entering ContentCodeBag ParseCodes(byte[])");
+ ContentCodeBag bag = new ContentCodeBag ();
+
+ // add some codes to bootstrap us
+ bag.AddCode ("mccr", "dmap.contentcodesresponse", ContentType.Container);
+ bag.AddCode ("mdcl", "dmap.dictionary", ContentType.Container);
+ bag.AddCode ("mcnm", "dmap.contentcodesnumber", ContentType.Long);
+ bag.AddCode ("mcna", "dmap.contentcodesname", ContentType.String);
+ bag.AddCode ("mcty", "dmap.contentcodestype", ContentType.Short);
+ bag.AddCode ("mstt", "dmap.status", ContentType.Long);
+
+ // some photo-specific codes
+ bag.AddCode ("ppro", "dpap.protocolversion", ContentType.Long);
+ bag.AddCode ("pret", "dpap.blah", ContentType.Container);
+
+ ContentNode node = ContentParser.Parse (bag, buffer);
+
+ foreach (ContentNode dictNode in (node.Value as ContentNode[])) {
+ Console.Write(node.Name + ' ' + node.Value + ' ');
+ if (dictNode.Name != "dmap.dictionary") {
+ continue;
+ }
+
+ ContentCode code = new ContentCode ();
+
+ foreach (ContentNode item in (dictNode.Value as ContentNode[])) {
+ switch (item.Name) {
+ case "dmap.contentcodesnumber":
+ code.Number = (int) item.Value;
+ break;
+ case "dmap.contentcodesname":
+ code.Name = (string) item.Value;
+ break;
+ case "dmap.contentcodestype":
+ code.Type = (ContentType) Enum.ToObject (typeof (ContentType), (short) item.Value);
+ break;
+ }
+ }
+
+ bag.codes[code.Number] = code;
+ }
+ Console.WriteLine("Leaving ContentCodeBag ParseCodes(byte[])");
+ return bag;
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/ContentFetcher.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/ContentFetcher.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,211 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Web;
+using System.Text;
+using System.Net;
+using System.Runtime.InteropServices;
+using ICSharpCode.SharpZipLib.GZip;
+
+namespace DPAP {
+
+ internal class ContentFetcher : IDisposable {
+ private IPAddress address;
+ private UInt16 port;
+ private int sessionId;
+ private int requestId = 10;
+
+ private DAAPCredentials creds = new DAAPCredentials ();
+ private List<WebRequest> requests = new List<WebRequest> ();
+
+ public string Username {
+ get { return creds.Username; }
+ set { creds.Username = value; }
+ }
+
+ public string Password {
+ get { return creds.Password; }
+ set { creds.Password = value; }
+ }
+
+ public int SessionId {
+ get { return sessionId; }
+ set { sessionId = value; }
+ }
+
+ public ContentFetcher (IPAddress address, UInt16 port) {
+ this.address = address;
+ this.port = port;
+ }
+
+ public void Dispose () {
+ KillAll ();
+ }
+
+ public void KillAll () {
+ lock (requests) {
+ foreach (WebRequest request in requests) {
+ request.Abort ();
+ }
+
+ requests.Clear ();
+ }
+ }
+
+ public byte[] Fetch (string path) {
+ return Fetch (path, null, null, 0);
+ }
+
+ public byte[] Fetch (string path, string query) {
+ return Fetch (path, query, null, 0);
+ }
+
+ public byte[] Fetch (string path, string query, WebHeaderCollection extraHeaders,
+ int requestId) {
+
+ HttpWebResponse response = FetchResponse (path, -1, query, extraHeaders, requestId, false);
+
+ MemoryStream data = new MemoryStream ();
+ BinaryReader reader = new BinaryReader (GetResponseStream (response));
+ try {
+ if (response.ContentLength < 0)
+ return null;
+
+ byte[] buf;
+ while (true) {
+ buf = reader.ReadBytes (8192);
+ if (buf.Length == 0)
+ break;
+
+ data.Write (buf, 0, buf.Length);
+ //Console.Write(buf.);
+ }
+
+ data.Flush ();
+ return data.GetBuffer ();
+ } finally {
+ data.Close ();
+ reader.Close ();
+ response.Close ();
+ }
+ }
+
+ public HttpWebResponse FetchResponse (string path, string query, WebHeaderCollection headers) {
+ return FetchResponse (path, -1, query, headers, ++requestId, false);
+ }
+
+ public HttpWebResponse FetchFile (string path, long offset) {
+ return FetchResponse (path, offset, null, null, ++requestId, true);
+ }
+
+ public HttpWebResponse FetchResponse (string path, long offset, string query,
+ WebHeaderCollection extraHeaders,
+ int requestId, bool disableKeepalive) {
+ UriBuilder builder = new UriBuilder ("http", address.ToString ());
+ builder.Port = port;
+ builder.Path = path;
+
+ if (sessionId != 0)
+ query = String.Format ("session-id={0}&", sessionId) + query;
+
+ if (query != null)
+ builder.Query += query;
+
+ HttpWebRequest request = (HttpWebRequest) WebRequest.Create (builder.Uri);
+ request.PreAuthenticate = true;
+ request.Timeout = System.Threading.Timeout.Infinite;
+ request.Headers.Add ("Accept-Encoding", "gzip");
+
+ if (offset > 0) {
+ request.AddRange ("bytes", (int) offset);
+ }
+
+ request.ServicePoint.ConnectionLimit = 3;
+
+ if (extraHeaders != null)
+ request.Headers = extraHeaders;
+
+ request.Accept = "*/*";
+
+ request.KeepAlive = !disableKeepalive;
+
+ string hash = Hasher.GenerateHash (3, builder.Uri.PathAndQuery, 2, requestId);
+
+ request.UserAgent = "iPhoto/4.01 (Macintosh; PPC)";
+ request.Headers.Set ("Client-DMAP-Version", "1.0");
+ request.Headers.Set ("Client-DPAP-Version", "1.0");
+ /*request.Headers.Set ("Client-DPAP-Validation", hash);
+ request.Headers.Set ("Client-DPAP-Access-Index", "2");
+ */
+ Console.Write(path + query);
+ Console.Write(request.Headers);
+
+ if (requestId >= 0)
+ request.Headers.Set ("Client-DPAP-Request-ID", requestId.ToString ());
+
+ request.Credentials = creds;
+ request.PreAuthenticate = true;
+
+ try {
+ lock (requests) {
+ requests.Add (request);
+ }
+ HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
+ Console.Write(response.StatusCode);
+ return response;
+ } finally {
+ lock (requests) {
+ requests.Remove (request);
+ }
+ }
+ }
+
+ public Stream GetResponseStream (HttpWebResponse response) {
+ if (response.ContentEncoding == "gzip") {
+ return new GZipInputStream (response.GetResponseStream ());
+ } else {
+ return response.GetResponseStream ();
+ }
+ }
+
+ private class DAAPCredentials : ICredentials {
+
+ private string username;
+ private string password;
+
+ public string Username {
+ get { return username; }
+ set { username = value; }
+ }
+
+ public string Password {
+ get { return password; }
+ set { password = value; }
+ }
+
+ public NetworkCredential GetCredential (Uri uri, string type) {
+ return new NetworkCredential (username == null ? "none" : username, password);
+ }
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/ContentParser.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/ContentParser.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,192 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Text;
+using System.Net;
+using System.Collections;
+
+namespace DPAP {
+
+ public class ContentException : ApplicationException {
+
+ public ContentException (string msg) : base (msg) {
+ }
+ }
+
+ internal class ContentNode {
+ public string Name;
+ public object Value;
+
+ public ContentNode () {
+ }
+
+ public ContentNode (string name, params object[] values) {
+ this.Name = name;
+
+ ArrayList vals = new ArrayList ();
+ foreach (object v in values) {
+ if (v is ICollection) {
+ vals.AddRange ((ICollection) v);
+ } else {
+ vals.Add (v);
+ }
+ }
+
+ if (vals.Count == 1 && vals[0].GetType () != typeof (ContentNode))
+ this.Value = vals[0];
+ else
+ this.Value = (object) vals.ToArray (typeof (ContentNode));
+ }
+
+ public void Dump () {
+ Dump (0);
+ }
+
+ private void Dump (int level) {
+ Console.WriteLine ("{0}Name: {1}", String.Empty.PadRight (level * 4), Name);
+
+ if (Value is ContentNode[]) {
+ foreach (ContentNode child in (Value as ContentNode[])) {
+ child.Dump (level + 1);
+ }
+ } else {
+ Console.WriteLine ("{0}Value ({1}): {2}", String.Empty.PadRight (level * 4),
+ Value.GetType (), Value);
+ Console.WriteLine ();
+ }
+ }
+
+ public ContentNode GetChild (string name) {
+ if (name == this.Name)
+ return this;
+
+ ContentNode[] children = Value as ContentNode[];
+ if (children == null)
+ return null;
+
+ foreach (ContentNode child in children) {
+ ContentNode needle = child.GetChild (name);
+ if (needle != null)
+ return needle;
+ }
+
+ return null;
+ }
+ }
+
+ internal class ContentParser {
+
+ private static ContentNode[] ParseChildren (ContentCodeBag bag, byte[] buffer,
+ int offset, int length) {
+ ArrayList children = new ArrayList ();
+
+ int position = offset;
+
+ while (position < offset + length) {
+ children.Add (Parse (bag, buffer, null, ref position));
+ }
+
+ return (ContentNode[]) children.ToArray (typeof (ContentNode));
+ }
+
+ public static ContentNode Parse (ContentCodeBag bag, byte[] buffer, string root,
+ ref int offset) {
+ Console.WriteLine("Entering ContentNode Pares (...)");
+ ContentNode node = new ContentNode ();
+//System.Console.WriteLine("debug1!");
+ int num = IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (buffer, offset));
+ // System.Console.WriteLine("debug2!");
+ ContentCode code = bag.Lookup (num);
+ Console.Write(code.Name);
+ // System.Console.WriteLine("debug3!");
+ if (code.Equals (ContentCode.Zero)) {
+ // probably a buggy server. fallback to our internal code bag
+ code = ContentCodeBag.Default.Lookup (num);
+ }
+
+ //System.Console.WriteLine("debug!4");
+ int length = IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (buffer, offset + 4));
+
+ if (code.Equals (ContentCode.Zero)) {
+ throw new ContentException (String.Format ("Failed to find content code for '{0}'. Data length is {1}",
+ ContentCodeBag.GetStringFormat (num), length));
+ }
+
+ node.Name = code.Name;
+
+ switch (code.Type) {
+ case ContentType.Char:
+ node.Value = (byte) buffer[offset + 8];
+ break;
+ case ContentType.Short:
+ node.Value = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (buffer, offset + 8));
+ break;
+ case ContentType.SignedLong:
+ case ContentType.Long:
+ node.Value = IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (buffer, offset + 8));
+ break;
+ case ContentType.LongLong:
+ node.Value = IPAddress.NetworkToHostOrder (BitConverter.ToInt64 (buffer, offset + 8));
+ break;
+ case ContentType.String:
+ node.Value = Encoding.UTF8.GetString (buffer, offset + 8, length);
+ break;
+ case ContentType.Date:
+ node.Value = Utility.ToDateTime (IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (buffer, offset + 8)));
+ break;
+ case ContentType.Version:
+ int major = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (buffer, offset + 8));
+ int minor = (int) buffer[offset + 10];
+ int micro = (int) buffer[offset + 11];
+
+ node.Value = new Version (major, minor, micro);
+ break;
+ case ContentType.Container:
+ node.Value = ParseChildren (bag, buffer, offset + 8, length);
+ break;
+ default:
+ throw new ContentException (String.Format ("Unknown content type '{0}' for '{1}'",
+ code.Type, code.Name));
+ }
+
+ offset += length + 8;
+Console.WriteLine("Leaving ContentNode Pares (...)");
+ if (root != null) {
+ ContentNode rootNode = node.GetChild (root);
+
+ if (rootNode == null)
+ throw new ContentException (String.Format ("Could not find root node '{0}'", root));
+
+ return rootNode;
+ } else {
+ return node;
+ }
+ }
+
+ public static ContentNode Parse (ContentCodeBag bag, byte[] buffer, string root) {
+ int offset = 0;
+ return Parse (bag, buffer, root, ref offset);
+ }
+
+ public static ContentNode Parse (ContentCodeBag bag, byte[] buffer) {
+ return Parse (bag, buffer, null);
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/ContentWriter.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/ContentWriter.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,110 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+using System;
+using System.Text;
+using System.Net;
+using System.IO;
+
+namespace DPAP {
+
+ internal class ContentWriter {
+
+
+ private static void Write (ContentCodeBag bag, ContentNode node, BinaryWriter writer) {
+
+ ContentCode code = bag.Lookup (node.Name);
+ if (code.Equals (ContentCode.Zero)) {
+ throw new ContentException ("Failed to get content code for: " + node.Name);
+ }
+
+ writer.Write (IPAddress.HostToNetworkOrder (code.Number));
+
+ switch (code.Type) {
+ case ContentType.Char:
+ writer.Write (IPAddress.HostToNetworkOrder (1));
+ writer.Write ((byte) node.Value);
+ break;
+ case ContentType.Short:
+ writer.Write (IPAddress.HostToNetworkOrder (2));
+ writer.Write (IPAddress.HostToNetworkOrder ((short) node.Value));
+ break;
+ case ContentType.SignedLong:
+ case ContentType.Long:
+ writer.Write (IPAddress.HostToNetworkOrder (4));
+ writer.Write (IPAddress.HostToNetworkOrder ((int) node.Value));
+ break;
+ case ContentType.LongLong:
+ writer.Write (IPAddress.HostToNetworkOrder (8));
+ writer.Write (IPAddress.HostToNetworkOrder ((long) node.Value));
+ break;
+ case ContentType.String:
+ byte[] data = Encoding.UTF8.GetBytes ((string) node.Value);
+ writer.Write (IPAddress.HostToNetworkOrder (data.Length));
+ writer.Write (data);
+ break;
+ case ContentType.Date:
+ writer.Write (IPAddress.HostToNetworkOrder (4));
+ writer.Write (IPAddress.HostToNetworkOrder (Utility.FromDateTime ((DateTime) node.Value)));
+ break;
+ case ContentType.Version:
+ Version version = (Version) node.Value;
+ writer.Write (IPAddress.HostToNetworkOrder (4));
+
+ writer.Write ((short) IPAddress.HostToNetworkOrder ((short) version.Major));
+ writer.Write ((byte) version.Minor);
+ writer.Write ((byte) version.Build);
+ break;
+ case ContentType.Container:
+ MemoryStream childStream = new MemoryStream ();
+ BinaryWriter childWriter = new BinaryWriter (childStream);
+
+ foreach (ContentNode child in (ContentNode[]) node.Value) {
+ Write (bag, child, childWriter);
+ }
+
+ childWriter.Flush ();
+ byte[] bytes = childStream.GetBuffer ();
+ int len = (int) childStream.Length;
+
+ writer.Write (IPAddress.HostToNetworkOrder (len));
+ writer.Write (bytes, 0, len);
+ childWriter.Close ();
+ break;
+ default:
+ Console.Error.WriteLine ("Cannot write node of type: " + code.Type);
+ break;
+ }
+ }
+
+ public static byte[] Write (ContentCodeBag bag, ContentNode node) {
+ MemoryStream stream = new MemoryStream ();
+ BinaryWriter writer = new BinaryWriter (stream);
+ Write (bag, node, writer);
+ writer.Flush ();
+
+ byte[] buf = stream.GetBuffer ();
+ long len = stream.Length;
+ writer.Close ();
+
+ byte[] ret = new byte[len];
+ Array.Copy (buf, ret, len);
+ return ret;
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/Database.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Database.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,505 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Net;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Threading;
+
+namespace DPAP {
+
+ public delegate void TrackHandler (object o, TrackArgs args);
+
+ public class TrackArgs : EventArgs {
+ private Track track;
+
+ public Track Track {
+ get { return track; }
+ }
+
+ public TrackArgs (Track track) {
+ this.track = track;
+ }
+ }
+
+ public delegate void PlaylistHandler (object o, PlaylistArgs args);
+
+ public class PlaylistArgs : EventArgs {
+ private Playlist pl;
+
+ public Playlist Playlist {
+ get { return pl; }
+ }
+
+ public PlaylistArgs (Playlist pl) {
+ this.pl = pl;
+ }
+ }
+
+ public class Database : ICloneable {
+
+ private const int ChunkLength = 8192;
+ private const string TrackQuery = "meta=dmap.itemid,dmap.itemname,dmap.itemkind,dmap.persistentid," +
+ "daap.songalbum,daap.songgrouping,daap.songartist,daap.songbitrate," +
+ "daap.songbeatsperminute,daap.songcomment,daap.songcodectype," +
+ "daap.songcodecsubtype,daap.songcompilation,daap.songcomposer," +
+ "daap.songdateadded,daap.songdatemodified,daap.songdisccount," +
+ "daap.songdiscnumber,daap.songdisabled,daap.songeqpreset," +
+ "daap.songformat,daap.songgenre,daap.songdescription," +
+ "daap.songsamplerate,daap.songsize,daap.songstarttime," +
+ "daap.songstoptime,daap.songtime,daap.songtrackcount," +
+ "daap.songtracknumber,daap.songuserrating,daap.songyear," +
+ "daap.songdatakind,daap.songdataurl,com.apple.itunes.norm-volume," +
+ "com.apple.itunes.itms-songid,com.apple.itunes.itms-artistid," +
+ "com.apple.itunes.itms-playlistid,com.apple.itunes.itms-composerid," +
+ "com.apple.itunes.itms-genreid";
+
+ private static int nextid = 1;
+ private Client client;
+ private int id;
+ private long persistentId;
+ private string name;
+
+ private List<Track> tracks = new List<Track> ();
+ private List<Playlist> playlists = new List<Playlist> ();
+ private Playlist basePlaylist = new Playlist ();
+ private int nextTrackId = 1;
+
+ public event TrackHandler TrackAdded;
+ public event TrackHandler TrackRemoved;
+ public event PlaylistHandler PlaylistAdded;
+ public event PlaylistHandler PlaylistRemoved;
+
+ public int Id {
+ get { return id; }
+ }
+
+ public string Name {
+ get { return name; }
+ set {
+ name = value;
+ basePlaylist.Name = value;
+ }
+ }
+
+ public IList<Track> Tracks {
+ get {
+ return new ReadOnlyCollection<Track> (tracks);
+ }
+ }
+
+ public int TrackCount {
+ get { return tracks.Count; }
+ }
+
+ public Track TrackAt(int index)
+ {
+ return tracks[index] as Track;
+ }
+
+ public IList<Playlist> Playlists {
+ get {
+ return new ReadOnlyCollection<Playlist> (playlists);
+ }
+ }
+
+ internal Client Client {
+ get { return client; }
+ }
+
+ private Database () {
+ this.id = nextid++;
+ }
+
+ public Database (string name) : this () {
+ this.Name = name;
+ }
+
+ internal Database (Client client, ContentNode dbNode) : this () {
+ this.client = client;
+
+ Parse (dbNode);
+ }
+
+ private void Parse (ContentNode dbNode) {
+ foreach (ContentNode item in (ContentNode[]) dbNode.Value) {
+
+ switch (item.Name) {
+ case "dmap.itemid":
+ id = (int) item.Value;
+ break;
+ case "dmap.persistentid":
+ persistentId = (long) item.Value;
+ break;
+ case "dmap.itemname":
+ name = (string) item.Value;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public Track LookupTrackById (int id) {
+ foreach (Track track in tracks) {
+ if (track.Id == id)
+ return track;
+ }
+
+ return null;
+ }
+
+ public Playlist LookupPlaylistById (int id) {
+ if (id == basePlaylist.Id)
+ return basePlaylist;
+
+ foreach (Playlist pl in playlists) {
+ if (pl.Id == id)
+ return pl;
+ }
+
+ return null;
+ }
+
+ internal ContentNode ToTracksNode (string[] fields, int[] deletedIds) {
+
+ ArrayList trackNodes = new ArrayList ();
+ foreach (Track track in tracks) {
+ trackNodes.Add (track.ToNode (fields));
+ }
+
+ ArrayList deletedNodes = null;
+
+ if (deletedIds.Length > 0) {
+ deletedNodes = new ArrayList ();
+
+ foreach (int id in deletedIds) {
+ deletedNodes.Add (new ContentNode ("dmap.itemid", id));
+ }
+ }
+
+ ArrayList children = new ArrayList ();
+ children.Add (new ContentNode ("dmap.status", 200));
+ children.Add (new ContentNode ("dmap.updatetype", deletedNodes == null ? (byte) 0 : (byte) 1));
+ children.Add (new ContentNode ("dmap.specifiedtotalcount", tracks.Count));
+ children.Add (new ContentNode ("dmap.returnedcount", tracks.Count));
+ children.Add (new ContentNode ("dmap.listing", trackNodes));
+
+ if (deletedNodes != null) {
+ children.Add (new ContentNode ("dmap.deletedidlisting", deletedNodes));
+ }
+
+ return new ContentNode ("daap.databasesongs", children);
+ }
+
+ internal ContentNode ToPlaylistsNode () {
+ ArrayList nodes = new ArrayList ();
+
+ nodes.Add (basePlaylist.ToNode (true));
+
+ foreach (Playlist pl in playlists) {
+ nodes.Add (pl.ToNode (false));
+ }
+
+ return new ContentNode ("daap.databaseplaylists",
+ new ContentNode ("dmap.status", 200),
+ new ContentNode ("dmap.updatetype", (byte) 0),
+ new ContentNode ("dmap.specifiedtotalcount", nodes.Count),
+ new ContentNode ("dmap.returnedcount", nodes.Count),
+ new ContentNode ("dmap.listing", nodes));
+ }
+
+ internal ContentNode ToDatabaseNode () {
+ return new ContentNode ("dmap.listingitem",
+ new ContentNode ("dmap.itemid", id),
+ new ContentNode ("dmap.persistentid", (long) id),
+ new ContentNode ("dmap.itemname", name),
+ new ContentNode ("dmap.itemcount", tracks.Count),
+ new ContentNode ("dmap.containercount", playlists.Count + 1));
+ }
+
+ public void Clear () {
+ if (client != null)
+ throw new InvalidOperationException ("cannot clear client databases");
+
+ ClearPlaylists ();
+ ClearTracks ();
+ }
+
+ private void ClearPlaylists () {
+ foreach (Playlist pl in new List<Playlist> (playlists)) {
+ RemovePlaylist (pl);
+ }
+ }
+
+ private void ClearTracks () {
+ foreach (Track track in new List<Track> (tracks)) {
+ RemoveTrack (track);
+ }
+ }
+
+ private bool IsUpdateResponse (ContentNode node) {
+ return node.Name == "dmap.updateresponse";
+ }
+
+ private void RefreshPlaylists (string revquery) {
+ byte[] playlistsData;
+
+ try {
+ playlistsData = client.Fetcher.Fetch (String.Format ("/databases/{0}/containers", id, revquery));
+ } catch (WebException) {
+ return;
+ }
+
+ ContentNode playlistsNode = ContentParser.Parse (client.Bag, playlistsData);
+
+ if (IsUpdateResponse (playlistsNode))
+ return;
+
+ // handle playlist additions/changes
+ ArrayList plids = new ArrayList ();
+
+ foreach (ContentNode playlistNode in (ContentNode[]) playlistsNode.GetChild ("dmap.listing").Value) {
+ Playlist pl = Playlist.FromNode (playlistNode);
+
+ if (pl != null) {
+ plids.Add (pl.Id);
+ Playlist existing = LookupPlaylistById (pl.Id);
+
+ if (existing == null) {
+ AddPlaylist (pl);
+ } else {
+ existing.Update (pl);
+ }
+ }
+ }
+
+ // delete playlists that no longer exist
+ foreach (Playlist pl in new List<Playlist> (playlists)) {
+ if (!plids.Contains (pl.Id)) {
+ RemovePlaylist (pl);
+ }
+ }
+
+ plids = null;
+
+ // add/remove tracks in the playlists
+ foreach (Playlist pl in playlists) {
+ byte[] playlistTracksData = client.Fetcher.Fetch (String.Format ("/databases/{0}/containers/{1}/items",
+ id, pl.Id), revquery);
+ ContentNode playlistTracksNode = ContentParser.Parse (client.Bag, playlistTracksData);
+
+ if (IsUpdateResponse (playlistTracksNode))
+ return;
+
+ if ((byte) playlistTracksNode.GetChild ("dmap.updatetype").Value == 1) {
+
+ // handle playlist track deletions
+ ContentNode deleteList = playlistTracksNode.GetChild ("dmap.deletedidlisting");
+
+ if (deleteList != null) {
+ foreach (ContentNode deleted in (ContentNode[]) deleteList.Value) {
+ int index = pl.LookupIndexByContainerId ((int) deleted.Value);
+
+ if (index < 0)
+ continue;
+
+ pl.RemoveAt (index);
+ }
+ }
+ }
+
+ // add new tracks, or reorder existing ones
+
+ int plindex = 0;
+ foreach (ContentNode plTrackNode in (ContentNode[]) playlistTracksNode.GetChild ("dmap.listing").Value) {
+ Track pltrack = null;
+ int containerId = 0;
+ Track.FromPlaylistNode (this, plTrackNode, out pltrack, out containerId);
+
+ if (pl[plindex] != null && pl.GetContainerId (plindex) != containerId) {
+ pl.RemoveAt (plindex);
+ pl.InsertTrack (plindex, pltrack, containerId);
+ } else if (pl[plindex] == null) {
+ pl.InsertTrack (plindex, pltrack, containerId);
+ }
+
+ plindex++;
+ }
+ }
+ }
+
+ private void RefreshTracks (string revquery) {
+ byte[] tracksData = client.Fetcher.Fetch (String.Format ("/databases/{0}/items", id),
+ TrackQuery + "&" + revquery);
+ ContentNode tracksNode = ContentParser.Parse (client.Bag, tracksData);
+
+ if (IsUpdateResponse (tracksNode))
+ return;
+
+ // handle track additions/changes
+ foreach (ContentNode trackNode in (ContentNode[]) tracksNode.GetChild ("dmap.listing").Value) {
+ Track track = Track.FromNode (trackNode);
+ Track existing = LookupTrackById (track.Id);
+
+ if (existing == null)
+ AddTrack (track);
+ else
+ existing.Update (track);
+ }
+
+ if ((byte) tracksNode.GetChild ("dmap.updatetype").Value == 1) {
+
+ // handle track deletions
+ ContentNode deleteList = tracksNode.GetChild ("dmap.deletedidlisting");
+
+ if (deleteList != null) {
+ foreach (ContentNode deleted in (ContentNode[]) deleteList.Value) {
+ Track track = LookupTrackById ((int) deleted.Value);
+
+ if (track != null)
+ RemoveTrack (track);
+ }
+ }
+ }
+ }
+
+ internal void Refresh (int newrev) {
+ if (client == null)
+ throw new InvalidOperationException ("cannot refresh server databases");
+
+ string revquery = null;
+
+ if (client.Revision != 0)
+ revquery = String.Format ("revision-number={0}&delta={1}", newrev, newrev - client.Revision);
+
+ RefreshTracks (revquery);
+ RefreshPlaylists (revquery);
+ }
+
+ private HttpWebResponse FetchTrack (Track track, long offset) {
+ return client.Fetcher.FetchFile (String.Format ("/databases/{0}/items/{1}.{2}", id, track.Id, track.Format),
+ offset);
+ }
+
+ public Stream StreamTrack (Track track, out long length) {
+ return StreamTrack (track, -1, out length);
+ }
+
+ public Stream StreamTrack (Track track, long offset, out long length) {
+ HttpWebResponse response = FetchTrack (track, offset);
+ length = response.ContentLength;
+ return response.GetResponseStream ();
+ }
+
+ public void DownloadTrack (Track track, string dest) {
+
+ BinaryWriter writer = new BinaryWriter (File.Open (dest, FileMode.Create));
+
+ try {
+ long len;
+ using (BinaryReader reader = new BinaryReader (StreamTrack (track, out len))) {
+ int count = 0;
+ byte[] buf = new byte[ChunkLength];
+
+ do {
+ count = reader.Read (buf, 0, ChunkLength);
+ writer.Write (buf, 0, count);
+ } while (count != 0);
+ }
+ } finally {
+ writer.Close ();
+ }
+ }
+
+ public void AddTrack (Track track) {
+ if (track.Id == 0)
+ track.SetId (nextTrackId++);
+
+ tracks.Add (track);
+ basePlaylist.AddTrack (track);
+
+ if (TrackAdded != null)
+ TrackAdded (this, new TrackArgs (track));
+ }
+
+ public void RemoveTrack (Track track) {
+ tracks.Remove (track);
+ basePlaylist.RemoveTrack (track);
+
+ foreach (Playlist pl in playlists) {
+ pl.RemoveTrack (track);
+ }
+
+ if (TrackRemoved != null)
+ TrackRemoved (this, new TrackArgs (track));
+ }
+
+ public void AddPlaylist (Playlist pl) {
+ playlists.Add (pl);
+
+ if (PlaylistAdded != null)
+ PlaylistAdded (this, new PlaylistArgs (pl));
+ }
+
+ public void RemovePlaylist (Playlist pl) {
+ playlists.Remove (pl);
+
+ if (PlaylistRemoved != null)
+ PlaylistRemoved (this, new PlaylistArgs (pl));
+ }
+
+ private Playlist ClonePlaylist (Database db, Playlist pl) {
+ Playlist clonePl = new Playlist (pl.Name);
+ clonePl.Id = pl.Id;
+
+ IList<Track> pltracks = pl.Tracks;
+ for (int i = 0; i < pltracks.Count; i++) {
+ clonePl.AddTrack (db.LookupTrackById (pltracks[i].Id), pl.GetContainerId (i));
+ }
+
+ return clonePl;
+ }
+
+ public object Clone () {
+ Database db = new Database (this.name);
+ db.id = id;
+ db.persistentId = persistentId;
+
+ List<Track> cloneTracks = new List<Track> ();
+ foreach (Track track in tracks) {
+ cloneTracks.Add ((Track) track.Clone ());
+ }
+
+ db.tracks = cloneTracks;
+
+ List<Playlist> clonePlaylists = new List<Playlist> ();
+ foreach (Playlist pl in playlists) {
+ clonePlaylists.Add (ClonePlaylist (db, pl));
+ }
+
+ db.playlists = clonePlaylists;
+ db.basePlaylist = ClonePlaylist (db, basePlaylist);
+ return db;
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/Discovery.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Discovery.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,135 @@
+// Discovery.cs created with MonoDevelop
+// User: andrzej at 09:58Â2008-06-01
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+using System;
+using System.Net;
+using System.Text;
+using System.Collections;
+using Mono.Zeroconf;
+
+namespace DPAP {
+
+ public delegate void ServiceHandler (object o, ServiceArgs args);
+
+ public class ServiceArgs : EventArgs {
+
+ private Service service;
+
+ public Service Service {
+ get { return service; }
+ }
+
+ public ServiceArgs (Service service) {
+ this.service = service;
+ }
+ }
+
+
+ public class Service {
+ private IPAddress address;
+ private ushort port;
+ private string name;
+ private bool isprotected;
+ private string machineId;
+
+ public IPAddress Address {
+ get { return address; }
+ }
+
+ public ushort Port {
+ get { return port; }
+ }
+
+ public string Name {
+ get { return name; }
+ }
+
+ public bool IsProtected {
+ get { return isprotected; }
+ }
+
+ public string MachineId {
+ get { return machineId; }
+ }
+
+ public Service (IPAddress address, ushort port, string name, bool isprotected, string machineId) {
+ this.address = address;
+ this.port = port;
+ this.name = name;
+ this.isprotected = isprotected;
+ this.machineId = machineId;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}:{1} ({2})", Address, Port, Name);
+ }
+ }
+
+ public class ServiceDiscovery{
+
+ private ServiceBrowser browser;
+ private Hashtable services = new Hashtable ();
+
+ public event ServiceHandler Found;
+ public event ServiceHandler Removed;
+
+ public IEnumerable Services {
+ get { return services; }
+ }
+
+ //
+ // Configure the code that will be called back when the information
+ // becomes available
+ //
+
+ public void Start(){
+ browser = new ServiceBrowser();
+ browser.ServiceAdded += OnServiceAdded;
+ browser.Browse("_dpap._tcp","local");
+ }
+
+ private void OnServiceAdded(object o, ServiceBrowseEventArgs args){
+ Console.WriteLine("Found Service: {0}", args.Service.Name);
+ args.Service.Resolved += OnServiceResolved;
+ args.Service.Resolve ();
+ }
+
+ private void OnServiceResolved(object o, ServiceResolvedEventArgs args){
+
+ IResolvableService s = (IResolvableService)args.Service;
+
+ string name = s.Name;
+ string machineId = null;
+ bool pwRequired = false;
+
+ Console.WriteLine("Resolved Service: {0} - {1}:{2} ({3} TXT record entries)",
+ s.FullName, s.HostEntry.AddressList[0], s.Port, s.TxtRecord.Count);
+
+ if (name.EndsWith ("_PW")) {
+ name = name.Substring (0, name.Length - 3);
+ pwRequired = true;
+ }
+
+ foreach(TxtRecordItem item in s.TxtRecord) {
+ if(item.Key.ToLower () == "password") {
+ pwRequired = item.ValueString.ToLower () == "true";
+ } else if (item.Key.ToLower () == "machine name") {
+ name = item.ValueString;
+ } else if (item.Key.ToLower () == "machine id") {
+ machineId = item.ValueString;
+ }
+ }
+
+ DPAP.Service svc = new DPAP.Service (s.HostEntry.AddressList[0], (ushort)s.Port,
+ name, pwRequired, machineId);
+
+ services[svc.Name] = svc;
+
+ if (Found != null)
+ Found (this, new ServiceArgs (svc));
+ }
+ }
+}
\ No newline at end of file
Added: trunk/dpap-sharp/lib/Hasher.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Hasher.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,219 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+/* Copyright (C) 2004 David Hammerton <david crazney net>
+ * Copyright (C) 2005 Jon Lech Johansen <jon nanocrew net>
+ *
+ * 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.
+ */
+
+using System;
+using System.Text;
+
+namespace DPAP {
+
+ internal class Hasher
+ {
+ private static byte [] _hasht42 = null;
+ private static byte [] _hasht45 = null;
+
+ private static byte [] _hexchars =
+ Encoding.ASCII.GetBytes( "0123456789ABCDEF" );
+ private static byte [] _copyright = Convert.FromBase64String(
+ "Q29weXJpZ2h0IDIwMDMgQXBwbGUgQ29tcHV0ZXIsIEluYy4=" );
+
+ private static void HashToString( byte [] hash, byte [] str, int offset )
+ {
+ for( int i = 0; i < hash.Length; i++ )
+ {
+ byte tmp = hash[ i ];
+ str[ i * 2 + 1 + offset ] = _hexchars[ tmp & 0xF ];
+ str[ i * 2 + 0 + offset ] = _hexchars[ (tmp >> 4) & 0xF ];
+ }
+ }
+
+ private static void TransformString( BrokenMD5 md5, string str, bool final )
+ {
+ byte [] tmp = Encoding.ASCII.GetBytes( str );
+
+ if( final )
+ md5.TransformFinalBlock( tmp, 0, tmp.Length );
+ else
+ md5.TransformBlock( tmp, 0, tmp.Length, tmp, 0 );
+ }
+
+ private static void GenerateTable42()
+ {
+ int i;
+
+ _hasht42 = new byte[ 256 * 32 ];
+
+ for( i = 0; i < 256; i++ )
+ {
+ BrokenMD5 md5 = new BrokenMD5( 0 );
+
+ if( ( i & 0x80 ) != 0 )
+ TransformString( md5, "Accept-Language", false );
+ else
+ TransformString( md5, "user-agent", false );
+
+ if( ( i & 0x40 ) != 0 )
+ TransformString( md5, "max-age", false );
+ else
+ TransformString( md5, "Authorization", false );
+
+ if( ( i & 0x20 ) != 0 )
+ TransformString( md5, "Client-DAAP-Version", false );
+ else
+ TransformString( md5, "Accept-Encoding", false );
+
+ if( ( i & 0x10 ) != 0 )
+ TransformString( md5, "daap.protocolversion", false );
+ else
+ TransformString( md5, "daap.songartist", false );
+
+ if( ( i & 0x08 ) != 0 )
+ TransformString( md5, "daap.songcomposer", false );
+ else
+ TransformString( md5, "daap.songdatemodified", false );
+
+ if( ( i & 0x04 ) != 0 )
+ TransformString( md5, "daap.songdiscnumber", false );
+ else
+ TransformString( md5, "daap.songdisabled", false );
+
+ if( ( i & 0x02 ) != 0 )
+ TransformString( md5, "playlist-item-spec", false );
+ else
+ TransformString( md5, "revision-number", false );
+
+ if( ( i & 0x01 ) != 0 )
+ TransformString( md5, "session-id", true );
+ else
+ TransformString( md5, "content-codes", true );
+
+ HashToString( md5.Hash, _hasht42, i * 32 );
+ }
+ }
+
+ private static void GenerateTable45()
+ {
+ int i;
+
+ _hasht45 = new byte[ 256 * 32 ];
+
+ for( i = 0; i < 256; i++ )
+ {
+ BrokenMD5 md5 = new BrokenMD5( 1 );
+
+ if( ( i & 0x40 ) != 0 )
+ TransformString( md5, "eqwsdxcqwesdc", false );
+ else
+ TransformString( md5, "op[;lm,piojkmn", false );
+
+ if( ( i & 0x20 ) != 0 )
+ TransformString( md5, "876trfvb 34rtgbvc", false );
+ else
+ TransformString( md5, "=-0ol.,m3ewrdfv", false );
+
+ if( ( i & 0x10 ) != 0 )
+ TransformString( md5, "87654323e4rgbv ", false );
+ else
+ TransformString( md5, "1535753690868867974342659792", false );
+
+ if( ( i & 0x08 ) != 0 )
+ TransformString( md5, "Song Name", false );
+ else
+ TransformString( md5, "DAAP-CLIENT-ID:", false );
+
+ if( ( i & 0x04 ) != 0 )
+ TransformString( md5, "111222333444555", false );
+ else
+ TransformString( md5, "4089961010", false );
+
+ if( ( i & 0x02 ) != 0 )
+ TransformString( md5, "playlist-item-spec", false );
+ else
+ TransformString( md5, "revision-number", false );
+
+ if( ( i & 0x01 ) != 0 )
+ TransformString( md5, "session-id", false );
+ else
+ TransformString( md5, "content-codes", false );
+
+ if( ( i & 0x80 ) != 0 )
+ TransformString( md5, "IUYHGFDCXWEDFGHN", true );
+ else
+ TransformString( md5, "iuytgfdxwerfghjm", true );
+
+ HashToString( md5.Hash, _hasht45, i * 32 );
+ }
+ }
+
+ public static string GenerateHash(int version_major, string url,
+ int hash_select, int request_id )
+ {
+ if( _hasht42 == null )
+ GenerateTable42();
+ if( _hasht45 == null )
+ GenerateTable45();
+
+ byte [] hashtable = (version_major == 3) ? _hasht45 : _hasht42;
+ BrokenMD5 md5 = new BrokenMD5( (version_major == 3) ? 1 : 0 );
+ byte [] hash = new byte[ 32 ];
+
+ byte [] tmp = Encoding.ASCII.GetBytes( url );
+ md5.TransformBlock( tmp, 0, tmp.Length, tmp, 0 );
+ md5.TransformBlock( _copyright, 0, _copyright.Length, _copyright, 0 );
+
+ if( request_id > 0 && version_major == 3 )
+ {
+ md5.TransformBlock( hashtable, hash_select * 32, 32,
+ hashtable, hash_select * 32 );
+ tmp = Encoding.ASCII.GetBytes( request_id.ToString() );
+ md5.TransformFinalBlock( tmp, 0, tmp.Length );
+ }
+ else
+ {
+ md5.TransformFinalBlock( hashtable, hash_select * 32, 32 );
+ }
+
+ HashToString( md5.Hash, hash, 0 );
+
+ return Encoding.ASCII.GetString( hash );
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/LoginException.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/LoginException.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,11 @@
+
+using System;
+
+namespace DPAP {
+
+ public class LoginException : ApplicationException {
+
+ public LoginException (string msg, Exception e) : base (msg, e) {
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/MyClass.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/MyClass.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,20 @@
+// MyClass.cs created with MonoDevelop
+// User: andrzej at 12:50Â2008-04-23
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+
+namespace dpapsharp
+{
+
+
+ public class MyClass
+ {
+
+ public MyClass()
+ {
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/Playlist.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Playlist.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,213 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace DPAP {
+
+ public delegate void PlaylistTrackHandler (object o, int index, Track track);
+
+ public class Playlist {
+
+ private static int nextid = 1;
+
+ private int id;
+ private string name = String.Empty;
+ private List<Track> tracks = new List<Track> ();
+ private List<int> containerIds = new List<int> ();
+
+ public event PlaylistTrackHandler TrackAdded;
+ public event PlaylistTrackHandler TrackRemoved;
+ public event EventHandler NameChanged;
+
+ public Track this[int index] {
+ get {
+ if (tracks.Count > index)
+ return tracks[index];
+ else
+ return null;
+ }
+ set { tracks[index] = value; }
+ }
+
+ public IList<Track> Tracks {
+ get { return new ReadOnlyCollection<Track> (tracks); }
+ }
+
+ internal int Id {
+ get { return id; }
+ set { id = value; }
+ }
+
+ public string Name {
+ get { return name; }
+ set {
+ name = value;
+ if (NameChanged != null)
+ NameChanged (this, new EventArgs ());
+ }
+ }
+
+ internal Playlist () {
+ id = nextid++;
+ }
+
+ public Playlist (string name) : this () {
+ this.name = name;
+ }
+
+ public void InsertTrack (int index, Track track) {
+ InsertTrack (index, track, tracks.Count + 1);
+ }
+
+ internal void InsertTrack (int index, Track track, int id) {
+ tracks.Insert (index, track);
+ containerIds.Insert (index, id);
+
+ if (TrackAdded != null)
+ TrackAdded (this, index, track);
+ }
+
+ public void Clear () {
+ tracks.Clear ();
+ }
+
+ public void AddTrack (Track track) {
+ AddTrack (track, tracks.Count + 1);
+ }
+
+ internal void AddTrack (Track track, int id) {
+ tracks.Add (track);
+ containerIds.Add (id);
+
+ if (TrackAdded != null)
+ TrackAdded (this, tracks.Count - 1, track);
+ }
+
+ public void RemoveAt (int index) {
+ Track track = (Track) tracks[index];
+ tracks.RemoveAt (index);
+ containerIds.RemoveAt (index);
+
+ if (TrackRemoved != null)
+ TrackRemoved (this, index, track);
+ }
+
+ public bool RemoveTrack (Track track) {
+ int index;
+ bool ret = false;
+
+ while ((index = IndexOf (track)) >= 0) {
+ ret = true;
+ RemoveAt (index);
+ }
+
+ return ret;
+ }
+
+ public int IndexOf (Track track) {
+ return tracks.IndexOf (track);
+ }
+
+ internal int GetContainerId (int index) {
+ return (int) containerIds[index];
+ }
+
+ internal ContentNode ToTracksNode (int[] deletedIds) {
+ ArrayList trackNodes = new ArrayList ();
+
+ for (int i = 0; i < tracks.Count; i++) {
+ Track track = tracks[i] as Track;
+ trackNodes.Add (track.ToPlaylistNode ((int) containerIds[i]));
+ }
+
+ ArrayList deletedNodes = null;
+ if (deletedIds.Length > 0) {
+ deletedNodes = new ArrayList ();
+
+ foreach (int id in deletedIds) {
+ deletedNodes.Add (new ContentNode ("dmap.itemid", id));
+ }
+ }
+
+ ArrayList children = new ArrayList ();
+ children.Add (new ContentNode ("dmap.status", 200));
+ children.Add (new ContentNode ("dmap.updatetype", deletedNodes == null ? (byte) 0 : (byte) 1));
+ children.Add (new ContentNode ("dmap.specifiedtotalcount", tracks.Count));
+ children.Add (new ContentNode ("dmap.returnedcount", tracks.Count));
+ children.Add (new ContentNode ("dmap.listing", trackNodes));
+
+ if (deletedNodes != null)
+ children.Add (new ContentNode ("dmap.deletedidlisting", deletedNodes));
+
+
+ return new ContentNode ("daap.playlistsongs", children);
+ }
+
+ internal ContentNode ToNode (bool basePlaylist) {
+
+ ArrayList nodes = new ArrayList ();
+
+ nodes.Add (new ContentNode ("dmap.itemid", id));
+ nodes.Add (new ContentNode ("dmap.persistentid", (long) id));
+ nodes.Add (new ContentNode ("dmap.itemname", name));
+ nodes.Add (new ContentNode ("dmap.itemcount", tracks.Count));
+ if (basePlaylist)
+ nodes.Add (new ContentNode ("daap.baseplaylist", (byte) 1));
+
+ return new ContentNode ("dmap.listingitem", nodes);
+ }
+
+ internal static Playlist FromNode (ContentNode node) {
+ Playlist pl = new Playlist ();
+
+ foreach (ContentNode child in (ContentNode[]) node.Value) {
+ switch (child.Name) {
+ case "daap.baseplaylist":
+ return null;
+ case "dmap.itemid":
+ pl.Id = (int) child.Value;
+ break;
+ case "dmap.itemname":
+ pl.Name = (string) child.Value;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return pl;
+ }
+
+ internal void Update (Playlist pl) {
+ if (pl.Name == name)
+ return;
+
+ Name = pl.Name;
+ }
+
+ internal int LookupIndexByContainerId (int id) {
+ return containerIds.IndexOf (id);
+ }
+ }
+
+}
Added: trunk/dpap-sharp/lib/Server.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Server.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,895 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Threading;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Net;
+using System.Net.Sockets;
+using System.Web;
+
+using Mono.Zeroconf;
+
+namespace DPAP {
+
+ internal delegate bool WebHandler (Socket client, string user, string path, NameValueCollection query, int range);
+
+ internal class WebServer {
+
+ private const int ChunkLength = 8192;
+
+ private UInt16 port;
+ private Socket server;
+ private WebHandler handler;
+ private bool running;
+ private List<NetworkCredential> creds = new List<NetworkCredential> ();
+ private ArrayList clients = new ArrayList ();
+ private string realm;
+ private AuthenticationMethod authMethod = AuthenticationMethod.None;
+
+ public ushort RequestedPort {
+ get { return port; }
+ set { port = value; }
+ }
+
+ public ushort BoundPort {
+ get { return (ushort) (server.LocalEndPoint as IPEndPoint).Port; }
+ }
+
+ public IList<NetworkCredential> Credentials {
+ get { return new ReadOnlyCollection<NetworkCredential> (creds); }
+ }
+
+ public AuthenticationMethod AuthenticationMethod {
+ get { return authMethod; }
+ set { authMethod = value; }
+ }
+
+ public string Realm {
+ get { return realm; }
+ set { realm = value; }
+ }
+
+ public WebServer (UInt16 port, WebHandler handler) {
+ this.port = port;
+ this.handler = handler;
+ }
+
+ public void Start () {
+ server = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
+ server.Bind (new IPEndPoint (IPAddress.Any, port));
+ server.Listen (10);
+
+ running = true;
+ Thread thread = new Thread (ServerLoop);
+ thread.IsBackground = true;
+ thread.Start ();
+ }
+
+ public void Stop () {
+ running = false;
+
+ if (server != null) {
+ server.Close ();
+ server = null;
+ }
+
+ foreach (Socket client in (ArrayList) clients.Clone ()) {
+ // do not pass go, do not collect $200...
+ client.Close ();
+ }
+ }
+
+ public void AddCredential (NetworkCredential cred) {
+ creds.Add (cred);
+ }
+
+ public void RemoveCredential (NetworkCredential cred) {
+ creds.Remove (cred);
+ }
+
+ public void WriteResponse (Socket client, ContentNode node) {
+ WriteResponse (client, HttpStatusCode.OK,
+ ContentWriter.Write (ContentCodeBag.Default, node));
+ }
+
+ public void WriteResponse (Socket client, HttpStatusCode code, string body) {
+ WriteResponse (client, code, Encoding.UTF8.GetBytes (body));
+ }
+
+ public void WriteResponse (Socket client, HttpStatusCode code, byte[] body) {
+ if (!client.Connected)
+ return;
+
+ using (BinaryWriter writer = new BinaryWriter (new NetworkStream (client, false))) {
+ writer.Write (Encoding.UTF8.GetBytes (String.Format ("HTTP/1.1 {0} {1}\r\n", (int) code, code.ToString ())));
+ writer.Write (Encoding.UTF8.GetBytes ("DAAP-Server: daap-sharp\r\n"));
+ writer.Write (Encoding.UTF8.GetBytes ("Content-Type: application/x-dmap-tagged\r\n"));
+ writer.Write (Encoding.UTF8.GetBytes (String.Format ("Content-Length: {0}\r\n", body.Length)));
+ writer.Write (Encoding.UTF8.GetBytes ("\r\n"));
+ writer.Write (body);
+ }
+ }
+
+ public void WriteResponseFile (Socket client, string file, long offset) {
+ FileInfo info = new FileInfo (file);
+
+ FileStream stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
+ WriteResponseStream (client, stream, info.Length, offset);
+ }
+
+ public void WriteResponseStream (Socket client, Stream response, long len) {
+ WriteResponseStream (client, response, len, -1);
+ }
+
+ public void WriteResponseStream (Socket client, Stream response, long len, long offset) {
+ using (BinaryWriter writer = new BinaryWriter (new NetworkStream (client, false))) {
+
+ if (offset > 0) {
+ writer.Write (Encoding.UTF8.GetBytes ("HTTP/1.1 206 Partial Content\r\n"));
+ writer.Write (Encoding.UTF8.GetBytes (String.Format ("Content-Range: bytes {0}-{1}/{2}\r\n",
+ offset, len, len + 1)));
+ writer.Write (Encoding.UTF8.GetBytes ("Accept-Range: bytes\r\n"));
+ len = len - offset;
+ } else {
+ writer.Write (Encoding.UTF8.GetBytes ("HTTP/1.1 200 OK\r\n"));
+ }
+
+ writer.Write (Encoding.UTF8.GetBytes (String.Format ("Content-Length: {0}\r\n", len)));
+ writer.Write (Encoding.UTF8.GetBytes ("\r\n"));
+
+ using (BinaryReader reader = new BinaryReader (response)) {
+ if (offset > 0) {
+ reader.BaseStream.Seek (offset, SeekOrigin.Begin);
+ }
+
+ long count = 0;
+ while (count < len) {
+ byte[] buf = reader.ReadBytes (Math.Min (ChunkLength, (int) len - (int) count));
+ if (buf.Length == 0) {
+ break;
+ }
+
+ writer.Write (buf);
+ count += buf.Length;
+ }
+ }
+ }
+ }
+
+ public void WriteAccessDenied (Socket client) {
+ string msg = "Authorization Required";
+
+ using (BinaryWriter writer = new BinaryWriter (new NetworkStream (client, false))) {
+ writer.Write (Encoding.UTF8.GetBytes ("HTTP/1.1 401 Denied\r\n"));
+ writer.Write (Encoding.UTF8.GetBytes (String.Format ("WWW-Authenticate: Basic realm=\"{0}\"",
+ realm)));
+ writer.Write (Encoding.UTF8.GetBytes ("Content-Type: text/plain\r\n"));
+ writer.Write (Encoding.UTF8.GetBytes (String.Format ("Content-Length: {0}\r\n", msg.Length)));
+ writer.Write (Encoding.UTF8.GetBytes ("\r\n"));
+ writer.Write (msg);
+ }
+ }
+
+ private bool IsValidAuth (string user, string pass) {
+ if (authMethod == AuthenticationMethod.None)
+ return true;
+
+ foreach (NetworkCredential cred in creds) {
+
+ if ((authMethod != AuthenticationMethod.UserAndPassword || cred.UserName == user) &&
+ cred.Password == pass)
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool HandleRequest (Socket client) {
+
+ if (!client.Connected)
+ return false;
+
+ bool ret = true;
+
+ using (StreamReader reader = new StreamReader (new NetworkStream (client, false))) {
+
+ string request = reader.ReadLine ();
+ if (request == null)
+ return false;
+
+ string line = null;
+ string user = null;
+ string password = null;
+ int range = -1;
+
+ // read the rest of the request
+ do {
+ line = reader.ReadLine ();
+
+ if (line == "Connection: close") {
+ ret = false;
+ } else if (line != null && line.StartsWith ("Authorization: Basic")) {
+ string[] splitLine = line.Split (' ');
+
+ if (splitLine.Length != 3)
+ continue;
+
+ string userpass = Encoding.UTF8.GetString (Convert.FromBase64String (splitLine[2]));
+
+ string[] splitUserPass = userpass.Split (new char[] {':'}, 2);
+ user = splitUserPass[0];
+ password = splitUserPass[1];
+ } else if (line != null && line.StartsWith ("Range: ")) {
+ // we currently expect 'Range: bytes=<offset>-'
+ string[] splitLine = line.Split ('=');
+
+ if (splitLine.Length != 2)
+ continue;
+
+ string rangestr = splitLine[1];
+ if (!rangestr.EndsWith ("-"))
+ continue;
+
+ try {
+ range = Int32.Parse (rangestr.Substring (0, rangestr.Length - 1));
+ } catch (FormatException) {
+ }
+ }
+ } while (line != String.Empty && line != null);
+
+
+ string[] splitRequest = request.Split ();
+ if (splitRequest.Length < 3) {
+ WriteResponse (client, HttpStatusCode.BadRequest, "Bad Request");
+ } else {
+ try {
+ string path = splitRequest[1];
+ if (!path.StartsWith ("daap://")) {
+ path = String.Format ("daap://localhost{0}", path);
+ }
+
+ Uri uri = new Uri (path);
+ NameValueCollection query = new NameValueCollection ();
+
+ if (uri.Query != null && uri.Query != String.Empty) {
+ string[] splitquery = uri.Query.Substring (1).Split ('&');
+
+ foreach (string queryItem in splitquery) {
+ if (queryItem == String.Empty)
+ continue;
+
+ string[] splitQueryItem = queryItem.Split ('=');
+ query[splitQueryItem[0]] = splitQueryItem[1];
+ }
+ }
+
+ if (authMethod != AuthenticationMethod.None && uri.AbsolutePath == "/login" &&
+ !IsValidAuth (user, password)) {
+ WriteAccessDenied (client);
+ return true;
+ }
+
+ return handler (client, user, uri.AbsolutePath, query, range);
+ } catch (IOException) {
+ ret = false;
+ } catch (Exception e) {
+ ret = false;
+ Console.Error.WriteLine ("Trouble handling request {0}: {1}", splitRequest[1], e);
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ private void HandleConnection (object o) {
+ Socket client = (Socket) o;
+
+ try {
+ while (HandleRequest (client)) { }
+ } catch (IOException) {
+ // ignore
+ } catch (Exception e) {
+ Console.Error.WriteLine ("Error handling request: " + e);
+ } finally {
+ clients.Remove (client);
+ client.Close ();
+ }
+ }
+
+ private void ServerLoop () {
+ while (true) {
+ try {
+ if (!running)
+ break;
+
+ Socket client = server.Accept ();
+ clients.Add (client);
+ ThreadPool.QueueUserWorkItem (HandleConnection, client);
+ } catch (SocketException) {
+ break;
+ }
+ }
+ }
+ }
+
+ internal class RevisionManager {
+
+ private Dictionary<int, List<Database>> revisions = new Dictionary<int, List<Database>> ();
+ private int current = 1;
+ private int limit = 3;
+
+ public int Current {
+ get { return current; }
+ }
+
+ public int HistoryLimit {
+ get { return limit; }
+ set { limit = value; }
+ }
+
+ public void AddRevision (List<Database> databases) {
+ revisions[++current] = databases;
+
+ if (revisions.Keys.Count > limit) {
+ // remove the oldest
+
+ int oldest = current;
+ foreach (int rev in revisions.Keys) {
+ if (rev < oldest) {
+ oldest = rev;
+ }
+ }
+
+ RemoveRevision (oldest);
+ }
+ }
+
+ public void RemoveRevision (int rev) {
+ revisions.Remove (rev);
+ }
+
+ public List<Database> GetRevision (int rev) {
+ if (rev == 0)
+ return revisions[current];
+ else
+ return revisions[rev];
+ }
+
+ public Database GetDatabase (int rev, int id) {
+ List<Database> dbs = GetRevision (rev);
+
+ if (dbs == null)
+ return null;
+
+ foreach (Database db in dbs) {
+ if (db.Id == id)
+ return db;
+ }
+
+ return null;
+ }
+ }
+
+ public class TrackRequestedArgs : EventArgs {
+
+ private string user;
+ private IPAddress host;
+ private Database db;
+ private Track track;
+
+ public string UserName {
+ get { return user; }
+ }
+
+ public IPAddress Host {
+ get { return host; }
+ }
+
+ public Database Database {
+ get { return db; }
+ }
+
+ public Track Track {
+ get { return track; }
+ }
+
+ public TrackRequestedArgs (string user, IPAddress host, Database db, Track track) {
+ this.user = user;
+ this.host = host;
+ this.db = db;
+ this.track = track;
+ }
+ }
+
+ public delegate void TrackRequestedHandler (object o, TrackRequestedArgs args);
+
+ public class Server {
+
+ internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes (30);
+
+ private static Regex dbItemsRegex = new Regex ("/databases/([0-9]*?)/items$");
+ private static Regex dbTrackRegex = new Regex ("/databases/([0-9]*?)/items/([0-9]*).*");
+ private static Regex dbContainersRegex = new Regex ("/databases/([0-9]*?)/containers$");
+ private static Regex dbContainerItemsRegex = new Regex ("/databases/([0-9]*?)/containers/([0-9]*?)/items$");
+
+ private WebServer ws;
+ private ArrayList databases = new ArrayList ();
+ private Dictionary<int, User> sessions = new Dictionary<int, User> ();
+ private Random random = new Random ();
+ private UInt16 port = 3689;
+ private ServerInfo serverInfo = new ServerInfo ();
+ private bool publish = true;
+ private int maxUsers = 0;
+ private bool running;
+ private string machineId;
+
+ private RegisterService zc_service;
+
+ private object eglock = new object ();
+ private RevisionManager revmgr = new RevisionManager ();
+
+ public event EventHandler Collision;
+ public event TrackRequestedHandler TrackRequested;
+ public event UserHandler UserLogin;
+ public event UserHandler UserLogout;
+
+ public IList<User> Users {
+ get {
+ lock (sessions) {
+ return new ReadOnlyCollection<User> (new List<User> (sessions.Values));
+ }
+ }
+ }
+
+ public string Name {
+ get { return serverInfo.Name; }
+ set {
+ serverInfo.Name = value;
+ ws.Realm = value;
+
+ if (publish)
+ RegisterService ();
+ }
+ }
+
+ public string MachineId {
+ get { return machineId; }
+ set { machineId = value; }
+ }
+
+ public UInt16 Port {
+ get { return port; }
+ set {
+ port = value;
+ ws.RequestedPort = value;
+ }
+ }
+
+ public bool IsPublished {
+ get { return publish; }
+ set {
+ publish = value;
+
+ if (running && publish)
+ RegisterService ();
+ else if (running && !publish)
+ UnregisterService ();
+ }
+ }
+
+ public bool IsRunning {
+ get { return running; }
+ }
+
+ public AuthenticationMethod AuthenticationMethod {
+ get { return serverInfo.AuthenticationMethod; }
+ set {
+ serverInfo.AuthenticationMethod = value;
+ ws.AuthenticationMethod = value;
+ }
+ }
+
+ public IList<NetworkCredential> Credentials {
+ get { return ws.Credentials; }
+ }
+
+ public int MaxUsers {
+ get { return maxUsers; }
+ set { maxUsers = value; }
+ }
+
+ public Server (string name) {
+ ws = new WebServer (port, OnHandleRequest);
+ serverInfo.Name = name;
+ ws.Realm = name;
+ }
+
+ public void Start () {
+ running = true;
+ ws.Start ();
+
+ if (publish)
+ RegisterService ();
+ }
+
+ public void Stop () {
+ running = false;
+
+ ws.Stop ();
+ UnregisterService ();
+
+ // get that thread to wake up and exit
+ lock (revmgr) {
+ Monitor.PulseAll (revmgr);
+ }
+ }
+
+ public void AddDatabase (Database db) {
+ databases.Add (db);
+ }
+
+ public void RemoveDatabase (Database db) {
+ databases.Remove (db);
+ }
+
+ public void AddCredential (NetworkCredential cred) {
+ ws.AddCredential (cred);
+ }
+
+ public void RemoveCredential (NetworkCredential cred) {
+ ws.RemoveCredential (cred);
+ }
+
+ public void Commit () {
+ List<Database> clones = new List<Database> ();
+ foreach (Database db in databases) {
+ clones.Add ((Database) db.Clone ());
+ }
+
+ lock (revmgr) {
+ revmgr.AddRevision (clones);
+ Monitor.PulseAll (revmgr);
+ }
+ }
+
+ private void RegisterService () {
+ lock (eglock) {
+ if (zc_service != null) {
+ UnregisterService ();
+ }
+
+ string auth = serverInfo.AuthenticationMethod == AuthenticationMethod.None ? "false" : "true";
+
+ zc_service = new RegisterService ();
+ zc_service.Name = serverInfo.Name;
+ zc_service.RegType = "_daap._tcp";
+ zc_service.Port = (short)ws.BoundPort;
+ zc_service.TxtRecord = new TxtRecord ();
+ zc_service.TxtRecord.Add ("Password", auth);
+ zc_service.TxtRecord.Add ("Machine Name", serverInfo.Name);
+
+ if (machineId != null) {
+ zc_service.TxtRecord.Add ("Machine ID", machineId);
+ }
+
+ zc_service.TxtRecord.Add ("txtvers", "1");
+ zc_service.Response += OnRegisterServiceResponse;
+ zc_service.Register ();
+ }
+ }
+
+ private void UnregisterService () {
+ lock (eglock) {
+ if (zc_service == null) {
+ return;
+ }
+
+ try {
+ zc_service.Dispose ();
+ } catch {
+ }
+ zc_service = null;
+ }
+ }
+
+ private void OnRegisterServiceResponse (object o, RegisterServiceEventArgs args) {
+ if (args.ServiceError == ServiceErrorCode.AlreadyRegistered && Collision != null) {
+ Collision (this, new EventArgs ());
+ }
+ }
+
+ private void ExpireSessions () {
+ lock (sessions) {
+ foreach (int s in new List<int> (sessions.Keys)) {
+ User user = sessions[s];
+
+ if (DateTime.Now - user.LastActionTime > DefaultTimeout) {
+ sessions.Remove (s);
+ OnUserLogout (user);
+ }
+ }
+ }
+ }
+
+ private void OnUserLogin (User user) {
+ UserHandler handler = UserLogin;
+ if (handler != null) {
+ try {
+ handler (this, new UserArgs (user));
+ } catch (Exception e) {
+ Console.Error.WriteLine ("Exception in UserLogin event handler: " + e);
+ }
+ }
+ }
+
+ private void OnUserLogout (User user) {
+ UserHandler handler = UserLogout;
+ if (handler != null) {
+ try {
+ handler (this, new UserArgs (user));
+ } catch (Exception e) {
+ Console.Error.WriteLine ("Exception in UserLogout event handler: " + e);
+ }
+ }
+ }
+
+ internal bool OnHandleRequest (Socket client, string username, string path, NameValueCollection query, int range) {
+
+ int session = 0;
+ if (query["session-id"] != null) {
+ session = Int32.Parse (query["session-id"]);
+ }
+
+ if (!sessions.ContainsKey (session) && path != "/server-info" && path != "/content-codes" &&
+ path != "/login") {
+ ws.WriteResponse (client, HttpStatusCode.Forbidden, "invalid session id");
+ return true;
+ }
+
+ if (session != 0) {
+ sessions[session].LastActionTime = DateTime.Now;
+ }
+
+ int clientRev = 0;
+ if (query["revision-number"] != null) {
+ clientRev = Int32.Parse (query["revision-number"]);
+ }
+
+ int delta = 0;
+ if (query["delta"] != null) {
+ delta = Int32.Parse (query["delta"]);
+ }
+
+ if (path == "/server-info") {
+ ws.WriteResponse (client, GetServerInfoNode ());
+ } else if (path == "/content-codes") {
+ ws.WriteResponse (client, ContentCodeBag.Default.ToNode ());
+ } else if (path == "/login") {
+ ExpireSessions ();
+
+ if (maxUsers > 0 && sessions.Count + 1 > maxUsers) {
+ ws.WriteResponse (client, HttpStatusCode.ServiceUnavailable, "too many users");
+ return true;
+ }
+
+ session = random.Next ();
+ User user = new User (DateTime.Now, (client.RemoteEndPoint as IPEndPoint).Address, username);
+
+ lock (sessions) {
+ sessions[session] = user;
+ }
+
+ ws.WriteResponse (client, GetLoginNode (session));
+ OnUserLogin (user);
+ } else if (path == "/logout") {
+ User user = sessions[session];
+
+ lock (sessions) {
+ sessions.Remove (session);
+ }
+
+ ws.WriteResponse (client, HttpStatusCode.OK, new byte[0]);
+ OnUserLogout (user);
+
+ return false;
+ } else if (path == "/databases") {
+ ws.WriteResponse (client, GetDatabasesNode ());
+ } else if (dbItemsRegex.IsMatch (path)) {
+ int dbid = Int32.Parse (dbItemsRegex.Match (path).Groups[1].Value);
+
+ Database curdb = revmgr.GetDatabase (clientRev, dbid);
+
+ if (curdb == null) {
+ ws.WriteResponse (client, HttpStatusCode.BadRequest, "invalid database id");
+ return true;
+ }
+
+ ArrayList deletedIds = new ArrayList ();
+
+ if (delta > 0) {
+ Database olddb = revmgr.GetDatabase (clientRev - delta, dbid);
+
+ if (olddb != null) {
+ foreach (Track track in olddb.Tracks) {
+ if (curdb.LookupTrackById (track.Id) == null)
+ deletedIds.Add (track.Id);
+ }
+ }
+ }
+
+ ContentNode node = curdb.ToTracksNode (query["meta"].Split (','),
+ (int[]) deletedIds.ToArray (typeof (int)));
+ ws.WriteResponse (client, node);
+ } else if (dbTrackRegex.IsMatch (path)) {
+ Match match = dbTrackRegex.Match (path);
+ int dbid = Int32.Parse (match.Groups[1].Value);
+ int trackid = Int32.Parse (match.Groups[2].Value);
+
+ Database db = revmgr.GetDatabase (clientRev, dbid);
+ if (db == null) {
+ ws.WriteResponse (client, HttpStatusCode.BadRequest, "invalid database id");
+ return true;
+ }
+
+ Track track = db.LookupTrackById (trackid);
+ if (track == null) {
+ ws.WriteResponse (client, HttpStatusCode.BadRequest, "invalid track id");
+ return true;
+ }
+
+ try {
+ try {
+ if (TrackRequested != null)
+ TrackRequested (this, new TrackRequestedArgs (username,
+ (client.RemoteEndPoint as IPEndPoint).Address,
+ db, track));
+ } catch {}
+
+ if (track.FileName != null) {
+ ws.WriteResponseFile (client, track.FileName, range);
+ } else if (db.Client != null) {
+ long trackLength = 0;
+ Stream trackStream = db.StreamTrack (track, out trackLength);
+
+ try {
+ ws.WriteResponseStream (client, trackStream, trackLength);
+ } catch (IOException) {
+ }
+ } else {
+ ws.WriteResponse (client, HttpStatusCode.InternalServerError, "no file");
+ }
+ } finally {
+ client.Close ();
+ }
+ } else if (dbContainersRegex.IsMatch (path)) {
+ int dbid = Int32.Parse (dbContainersRegex.Match (path).Groups[1].Value);
+
+ Database db = revmgr.GetDatabase (clientRev, dbid);
+ if (db == null) {
+ ws.WriteResponse (client, HttpStatusCode.BadRequest, "invalid database id");
+ return true;
+ }
+
+ ws.WriteResponse (client, db.ToPlaylistsNode ());
+ } else if (dbContainerItemsRegex.IsMatch (path)) {
+ Match match = dbContainerItemsRegex.Match (path);
+ int dbid = Int32.Parse (match.Groups[1].Value);
+ int plid = Int32.Parse (match.Groups[2].Value);
+
+ Database curdb = revmgr.GetDatabase (clientRev, dbid);
+ if (curdb == null) {
+ ws.WriteResponse (client, HttpStatusCode.BadRequest, "invalid database id");
+ return true;
+ }
+
+ Playlist curpl = curdb.LookupPlaylistById (plid);
+ if (curdb == null) {
+ ws.WriteResponse (client, HttpStatusCode.BadRequest, "invalid playlist id");
+ return true;
+ }
+
+ ArrayList deletedIds = new ArrayList ();
+ if (delta > 0) {
+ Database olddb = revmgr.GetDatabase (clientRev - delta, dbid);
+
+ if (olddb != null) {
+ Playlist oldpl = olddb.LookupPlaylistById (plid);
+
+ if (oldpl != null) {
+ IList<Track> oldplTracks = oldpl.Tracks;
+ for (int i = 0; i < oldplTracks.Count; i++) {
+ int id = oldpl.GetContainerId (i);
+ if (curpl.LookupIndexByContainerId (id) < 0) {
+ deletedIds.Add (id);
+ }
+ }
+ }
+ }
+ }
+
+ ws.WriteResponse (client, curpl.ToTracksNode ((int[]) deletedIds.ToArray (typeof (int))));
+ } else if (path == "/update") {
+ int retrev;
+
+ lock (revmgr) {
+ // if they have the current revision, wait for a change
+ if (clientRev == revmgr.Current) {
+ Monitor.Wait (revmgr);
+ }
+
+ retrev = revmgr.Current;
+ }
+
+ if (!running) {
+ ws.WriteResponse (client, HttpStatusCode.NotFound, "server has been stopped");
+ } else {
+ ws.WriteResponse (client, GetUpdateNode (retrev));
+ }
+ } else {
+ ws.WriteResponse (client, HttpStatusCode.Forbidden, "GO AWAY");
+ }
+
+ return true;
+ }
+
+ private ContentNode GetLoginNode (int id) {
+ return new ContentNode ("dmap.loginresponse",
+ new ContentNode ("dmap.status", 200),
+ new ContentNode ("dmap.sessionid", id));
+ }
+
+ private ContentNode GetServerInfoNode () {
+ return serverInfo.ToNode (databases.Count);
+ }
+
+ private ContentNode GetDatabasesNode () {
+ ArrayList databaseNodes = new ArrayList ();
+
+ List<Database> dbs = revmgr.GetRevision (revmgr.Current);
+ if (dbs != null) {
+ foreach (Database db in revmgr.GetRevision (revmgr.Current)) {
+ databaseNodes.Add (db.ToDatabaseNode ());
+ }
+ }
+
+ ContentNode node = new ContentNode ("daap.serverdatabases",
+ new ContentNode ("dmap.status", 200),
+ new ContentNode ("dmap.updatetype", (byte) 0),
+ new ContentNode ("dmap.specifiedtotalcount", databases.Count),
+ new ContentNode ("dmap.returnedcount", databases.Count),
+ new ContentNode ("dmap.listing", databaseNodes));
+
+ return node;
+ }
+
+ private ContentNode GetUpdateNode (int revision) {
+ return new ContentNode ("dmap.updateresponse",
+ new ContentNode ("dmap.status", 200),
+ new ContentNode ("dmap.serverrevision", revision));
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/ServerInfo.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/ServerInfo.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,97 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Text;
+using System.Net;
+
+namespace DPAP {
+
+ public enum AuthenticationMethod : byte {
+ None,
+ UserAndPassword,
+ Password,
+ }
+
+ internal class ServerInfo {
+
+ private string name;
+ private AuthenticationMethod authMethod;
+ private bool supportsUpdate;
+
+ public string Name {
+ get { return name; }
+ set { name = value; }
+ }
+
+ public AuthenticationMethod AuthenticationMethod {
+ get { return authMethod; }
+ set { authMethod = value; }
+ }
+
+ public bool SupportsUpdate {
+ get { return supportsUpdate; }
+ set { supportsUpdate = value; }
+ }
+
+ internal static ServerInfo FromNode (ContentNode node) {
+ ServerInfo info = new ServerInfo ();
+
+ if (node.Name != "dmap.serverinforesponse")
+ return null;
+
+ foreach (ContentNode child in (node.Value as ContentNode[])) {
+ switch (child.Name) {
+ case "dmap.itemname":
+ info.Name = (string) child.Value;
+ break;
+ case "dmap.authenticationmethod":
+ info.AuthenticationMethod = (AuthenticationMethod) child.Value;
+ break;
+ case "dmap.supportsupdate":
+ info.SupportsUpdate = (byte) child.Value == 1;
+ break;
+ }
+ }
+
+ return info;
+ }
+
+ internal ContentNode ToNode (int dbCount) {
+ return new ContentNode ("dmap.serverinforesponse",
+ new ContentNode ("dmap.status", 200),
+ new ContentNode ("dmap.protocolversion", new Version (2, 0, 2)),
+ new ContentNode ("daap.protocolversion", new Version (3, 0, 2)),
+ new ContentNode ("dmap.itemname", name),
+ new ContentNode ("dmap.loginrequired", (byte) 1),
+ new ContentNode ("dmap.authenticationmethod", (byte) authMethod),
+ new ContentNode ("dmap.timeoutinterval", (int) Server.DefaultTimeout.TotalSeconds),
+ new ContentNode ("dmap.supportsautologout", (byte) 1),
+ new ContentNode ("dmap.supportsupdate", (byte) 1),
+ new ContentNode ("dmap.supportspersistentids", (byte) 1),
+ new ContentNode ("dmap.supportsextensions", (byte) 1),
+ new ContentNode ("dmap.supportsbrowse", (byte) 1),
+ new ContentNode ("dmap.supportsquery", (byte) 1),
+ new ContentNode ("dmap.supportsindex", (byte) 1),
+ new ContentNode ("dmap.supportsresolve", (byte) 0),
+ new ContentNode ("dmap.databasescount", dbCount));
+ }
+
+ }
+}
Added: trunk/dpap-sharp/lib/Source.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Source.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,20 @@
+// Source.cs created with MonoDevelop
+// User: andrzej at 11:18Â2008-06-12
+//
+// To change standard headers go to Edit->Preferences->Coding->Standard Headers
+//
+
+using System;
+
+namespace DPAP
+{
+
+
+ public class Source
+ {
+
+ public Source()
+ {
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/Track.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Track.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,431 @@
+/*
+ * daap-sharp
+ * Copyright (C) 2005 James Willcox <snorp snorp net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Collections;
+
+namespace DPAP {
+
+ public class Track : ICloneable {
+
+ private string artist;
+ private string album;
+ private string title;
+ private int year;
+ private string format;
+ private TimeSpan duration;
+ private int id;
+ private int size;
+ private string genre;
+ private int trackNumber;
+ private int trackCount;
+ private string fileName;
+ private DateTime dateAdded = DateTime.Now;
+ private DateTime dateModified = DateTime.Now;
+ private short bitrate;
+
+ public event EventHandler Updated;
+
+ public string Artist {
+ get { return artist; }
+ set {
+ artist = value;
+ EmitUpdated ();
+ }
+ }
+
+ public string Album {
+ get { return album; }
+ set {
+ album = value;
+ EmitUpdated ();
+ }
+ }
+
+ public string Title {
+ get { return title; }
+ set {
+ title = value;
+ EmitUpdated ();
+ }
+ }
+
+ public int Year {
+ get { return year; }
+ set {
+ year = value;
+ EmitUpdated ();
+ }
+ }
+
+ public string Format {
+ get { return format; }
+ set {
+ format = value;
+ EmitUpdated ();
+ }
+ }
+
+ public TimeSpan Duration {
+ get { return duration; }
+ set {
+ duration = value;
+ EmitUpdated ();
+ }
+ }
+
+ public int Id {
+ get { return id; }
+ }
+
+ public int Size {
+ get { return size; }
+ set {
+ size = value;
+ EmitUpdated ();
+ }
+ }
+
+ public string Genre {
+ get { return genre; }
+ set {
+ genre = value;
+ EmitUpdated ();
+ }
+ }
+
+ public int TrackNumber {
+ get { return trackNumber; }
+ set {
+ trackNumber = value;
+ EmitUpdated ();
+ }
+ }
+
+ public int TrackCount {
+ get { return trackCount; }
+ set {
+ trackCount = value;
+ EmitUpdated ();
+ }
+ }
+
+ public string FileName {
+ get { return fileName; }
+ set {
+ fileName = value;
+ EmitUpdated ();
+ }
+ }
+
+ public DateTime DateAdded {
+ get { return dateAdded; }
+ set {
+ dateAdded = value;
+ EmitUpdated ();
+ }
+ }
+
+ public DateTime DateModified {
+ get { return dateModified; }
+ set {
+ dateModified = value;
+ EmitUpdated ();
+ }
+ }
+
+ public short BitRate {
+ get { return bitrate; }
+ set { bitrate = value; }
+ }
+
+ public object Clone () {
+ Track track = new Track ();
+ track.artist = artist;
+ track.album = album;
+ track.title = title;
+ track.year = year;
+ track.format = format;
+ track.duration = duration;
+ track.id = id;
+ track.size = size;
+ track.genre = genre;
+ track.trackNumber = trackNumber;
+ track.trackCount = trackCount;
+ track.fileName = fileName;
+ track.dateAdded = dateAdded;
+ track.dateModified = dateModified;
+ track.bitrate = bitrate;
+
+ return track;
+ }
+
+ public override string ToString () {
+ return String.Format ("{0} - {1}.{2} ({3}): {4}", artist, title, format, duration, id);
+ }
+
+ internal void SetId (int id) {
+ this.id = id;
+ }
+
+ internal ContentNode ToNode (string[] fields) {
+
+ ArrayList nodes = new ArrayList ();
+
+ foreach (string field in fields) {
+ object val = null;
+
+ switch (field) {
+ case "dmap.itemid":
+ val = id;
+ break;
+ case "dmap.itemname":
+ val = title;
+ break;
+ case "dmap.itemkind":
+ val = (byte) 2;
+ break;
+ case "dmap.persistentid":
+ val = (long) id;
+ break;
+ case "daap.songalbum":
+ val = album;
+ break;
+ case "daap.songgrouping":
+ val = String.Empty;
+ break;
+ case "daap.songartist":
+ val = artist;
+ break;
+ case "daap.songbitrate":
+ val = (short) bitrate;
+ break;
+ case "daap.songbeatsperminute":
+ val = (short) 0;
+ break;
+ case "daap.songcomment":
+ val = String.Empty;
+ break;
+ case "daap.songcompilation":
+ val = (byte) 0;
+ break;
+ case "daap.songcomposer":
+ val = String.Empty;
+ break;
+ case "daap.songdateadded":
+ val = dateAdded;
+ break;
+ case "daap.songdatemodified":
+ val = dateModified;
+ break;
+ case "daap.songdisccount":
+ val = (short) 0;
+ break;
+ case "daap.songdiscnumber":
+ val = (short) 0;
+ break;
+ case "daap.songdisabled":
+ val = (byte) 0;
+ break;
+ case "daap.songeqpreset":
+ val = String.Empty;
+ break;
+ case "daap.songformat":
+ val = format;
+ break;
+ case "daap.songgenre":
+ val = genre;
+ break;
+ case "daap.songdescription":
+ val = String.Empty;
+ break;
+ case "daap.songrelativevolume":
+ val = (int) 0;
+ break;
+ case "daap.songsamplerate":
+ val = 0;
+ break;
+ case "daap.songsize":
+ val = size;
+ break;
+ case "daap.songstarttime":
+ val = 0;
+ break;
+ case "daap.songstoptime":
+ val = 0;
+ break;
+ case "daap.songtime":
+ val = (int) duration.TotalMilliseconds;
+ break;
+ case "daap.songtrackcount":
+ val = (short) trackCount;
+ break;
+ case "daap.songtracknumber":
+ val = (short) trackNumber;
+ break;
+ case "daap.songuserrating":
+ val = (byte) 0;
+ break;
+ case "daap.songyear":
+ val = (short) year;
+ break;
+ case "daap.songdatakind":
+ val = (byte) 0;
+ break;
+ case "daap.songdataurl":
+ val = String.Empty;
+ break;
+ default:
+ break;
+ }
+
+ if (val != null) {
+ // iTunes wants this to go first, sigh
+ if (field == "dmap.itemkind")
+ nodes.Insert (0, new ContentNode (field, val));
+ else
+ nodes.Add (new ContentNode (field, val));
+ }
+ }
+
+ return new ContentNode ("dmap.listingitem", nodes);
+ }
+
+ internal static Track FromNode (ContentNode node) {
+ Track track = new Track ();
+
+ foreach (ContentNode field in (ContentNode[]) node.Value) {
+ switch (field.Name) {
+ case "dmap.itemid":
+ track.id = (int) field.Value;
+ break;
+ case "daap.songartist":
+ track.artist = (string) field.Value;
+ break;
+ case "dmap.itemname":
+ track.title = (string) field.Value;
+ break;
+ case "daap.songalbum":
+ track.album = (string) field.Value;
+ break;
+ case "daap.songtime":
+ track.duration = TimeSpan.FromMilliseconds ((int) field.Value);
+ break;
+ case "daap.songformat":
+ track.format = (string) field.Value;
+ break;
+ case "daap.songgenre":
+ track.genre = (string) field.Value;
+ break;
+ case "daap.songsize":
+ track.size = (int) field.Value;
+ break;
+ case "daap.songtrackcount":
+ track.trackCount = (short) field.Value;
+ break;
+ case "daap.songtracknumber":
+ track.trackNumber = (short) field.Value;
+ break;
+ case "daap.bitrate":
+ track.bitrate = (short) field.Value;
+ break;
+ case "daap.songdateadded":
+ track.dateAdded = (DateTime) field.Value;
+ break;
+ case "daap.songdatemodified":
+ track.dateModified = (DateTime) field.Value;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return track;
+ }
+
+ internal ContentNode ToPlaylistNode (int containerId) {
+ return new ContentNode ("dmap.listingitem",
+ new ContentNode ("dmap.itemkind", (byte) 2),
+ new ContentNode ("daap.songdatakind", (byte) 0),
+ new ContentNode ("dmap.itemid", Id),
+ new ContentNode ("dmap.containeritemid", containerId),
+ new ContentNode ("dmap.itemname", Title == null ? String.Empty : Title));
+ }
+
+ internal static void FromPlaylistNode (Database db, ContentNode node, out Track track, out int containerId) {
+ track = null;
+ containerId = 0;
+
+ foreach (ContentNode field in (ContentNode[]) node.Value) {
+ switch (field.Name) {
+ case "dmap.itemid":
+ track = db.LookupTrackById ((int) field.Value);
+ break;
+ case "dmap.containeritemid":
+ containerId = (int) field.Value;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private bool Equals (Track track) {
+ return artist == track.Artist &&
+ album == track.Album &&
+ title == track.Title &&
+ year == track.Year &&
+ format == track.Format &&
+ duration == track.Duration &&
+ size == track.Size &&
+ genre == track.Genre &&
+ trackNumber == track.TrackNumber &&
+ trackCount == track.TrackCount &&
+ dateAdded == track.DateAdded &&
+ dateModified == track.DateModified &&
+ bitrate == track.BitRate;
+ }
+
+ internal void Update (Track track) {
+ if (Equals (track))
+ return;
+
+ artist = track.Artist;
+ album = track.Album;
+ title = track.Title;
+ year = track.Year;
+ format = track.Format;
+ duration = track.Duration;
+ size = track.Size;
+ genre = track.Genre;
+ trackNumber = track.TrackNumber;
+ trackCount = track.TrackCount;
+ dateAdded = track.DateAdded;
+ dateModified = track.DateModified;
+ bitrate = track.BitRate;
+
+ EmitUpdated ();
+ }
+
+ private void EmitUpdated () {
+ if (Updated != null)
+ Updated (this, new EventArgs ());
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/User.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/User.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,50 @@
+using System;
+using System.Net;
+
+namespace DPAP {
+
+ public delegate void UserHandler (object o, UserArgs args);
+
+ public class UserArgs : EventArgs {
+
+ private User user;
+
+ public User User {
+ get { return user; }
+ }
+
+ public UserArgs (User user) {
+ this.user = user;
+ }
+ }
+
+ public class User {
+ private DateTime loginTime;
+ private DateTime lastAction;
+ private IPAddress address;
+ private string user;
+
+ public DateTime LoginTime {
+ get { return loginTime; }
+ }
+
+ public DateTime LastActionTime {
+ get { return lastAction; }
+ internal set { lastAction = value; }
+ }
+
+ public IPAddress Address {
+ get { return address; }
+ }
+
+ public string UserName {
+ get { return user; }
+ }
+
+ internal User (DateTime loginTime, IPAddress address, string user) {
+ this.loginTime = loginTime;
+ this.address = address;
+ this.user = user;
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/Utility.cs
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/Utility.cs Tue Jul 1 17:02:33 2008
@@ -0,0 +1,18 @@
+
+using System;
+
+namespace DPAP {
+
+ internal class Utility {
+
+ private static DateTime epoch = new DateTime (1970, 1, 1).ToLocalTime ();
+
+ public static DateTime ToDateTime (int time) {
+ return epoch.AddSeconds (time);
+ }
+
+ public static int FromDateTime (DateTime time) {
+ return (int) time.Subtract (epoch).TotalSeconds;
+ }
+ }
+}
Added: trunk/dpap-sharp/lib/content-codes
==============================================================================
Binary file. No diff available.
Added: trunk/dpap-sharp/lib/dpap-sharp.mdp
==============================================================================
--- (empty file)
+++ trunk/dpap-sharp/lib/dpap-sharp.mdp Tue Jul 1 17:02:33 2008
@@ -0,0 +1,49 @@
+<Project name="dpap-sharp" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
+ <Configurations active="Debug">
+ <Configuration name="Debug" ctype="DotNetProjectConfiguration">
+ <Output directory="bin/Debug" assemblyKeyFile="." assembly="dpap-sharp" />
+ <Build debugmode="True" target="Library" />
+ <Execution runwithwarnings="True" consolepause="False" runtime="MsNet" clr-version="Net_2_0" />
+ <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" definesymbols="DEBUG" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+ </Configuration>
+ <Configuration name="Release" ctype="DotNetProjectConfiguration">
+ <Output directory="bin/Release" assembly="dpap-sharp" />
+ <Build debugmode="False" target="Library" />
+ <Execution runwithwarnings="True" consolepause="False" runtime="MsNet" clr-version="Net_2_0" />
+ <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="True" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
+ </Configuration>
+ </Configurations>
+ <Contents>
+ <File name="MyClass.cs" subtype="Code" buildaction="Compile" />
+ <File name="AssemblyInfo.cs" subtype="Code" buildaction="Compile" />
+ <File name="gtk-gui/gui.stetic" subtype="Code" buildaction="EmbedAsResource" />
+ <File name="gtk-gui/generated.cs" subtype="Code" buildaction="Compile" />
+ <File name="Discovery.cs" subtype="Code" buildaction="Compile" />
+ <File name="Source.cs" subtype="Code" buildaction="Compile" />
+ <File name="Client.cs" subtype="Code" buildaction="Compile" />
+ <File name="ContentCodeBag.cs" subtype="Code" buildaction="Compile" />
+ <File name="ContentFetcher.cs" subtype="Code" buildaction="Compile" />
+ <File name="ContentParser.cs" subtype="Code" buildaction="Compile" />
+ <File name="ContentWriter.cs" subtype="Code" buildaction="Compile" />
+ <File name="Database.cs" subtype="Code" buildaction="Compile" />
+ <File name="ServerInfo.cs" subtype="Code" buildaction="Compile" />
+ <File name="Track.cs" subtype="Code" buildaction="Compile" />
+ <File name="Playlist.cs" subtype="Code" buildaction="Compile" />
+ <File name="AuthenticationException.cs" subtype="Code" buildaction="Compile" />
+ <File name="LoginException.cs" subtype="Code" buildaction="Compile" />
+ <File name="Hasher.cs" subtype="Code" buildaction="Compile" />
+ <File name="BrokenMD5.cs" subtype="Code" buildaction="Compile" />
+ <File name="Utility.cs" subtype="Code" buildaction="Compile" />
+ <File name="Server.cs" subtype="Code" buildaction="Compile" />
+ <File name="User.cs" subtype="Code" buildaction="Compile" />
+ </Contents>
+ <References>
+ <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+ <ProjectReference type="Gac" localcopy="True" refto="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+ <ProjectReference type="Gac" localcopy="True" refto="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+ <ProjectReference type="Gac" localcopy="True" refto="Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+ <ProjectReference type="Gac" localcopy="True" refto="Mono.Zeroconf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e60c4f4a95e1099e" />
+ <ProjectReference type="Gac" localcopy="True" refto="ICSharpCode.SharpZipLib, Version=2.84.0.0, Culture=neutral, PublicKeyToken=1b03e6acf1164f73" />
+ </References>
+ <GtkDesignInfo gtkVersion="2.12.0" />
+</Project>
\ No newline at end of file
Added: trunk/dpap-sharp/lib/dpap-sharp.pidb
==============================================================================
Binary file. No diff available.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]