f-spot r4127 - in trunk/dpap-sharp: . launcher lib



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]