/// <summary>
        /// Add multiple playlists to a playlist container.
        /// </summary>
        /// <param name="playlistContainer">The playlist container.</param>
        /// <param name="playlists">A List of playlists to be added.</param>
        /// <param name="position">The target position of the added playlists.</param>
        /// <returns></returns>
        public bool PlaylistContainerAddPlaylists(PlaylistContainer playlistContainer, List<Playlist> playlists, int position)
        {
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;
            /* Add the playlists for new checksum calculation. */
            playlistContainer.Playlists.InsertRange(position, playlists);

            /* Build a comma separated list of tracks and append '01' to every id!. */
            string playlistList = "";

            for(int i = 0; i < playlists.Count; i++){
                playlistList += ((i > 0)?",":"") + playlists[i].Id + "02";
            }

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <add>
                            <i> + " + position.ToString() + @"</i>
                            <items>" + playlistList + @"</items>
                        </add>
                    </ops>
                    <time>" + timestamp.ToString() + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" +
                           (playlistContainer.Revision + 1).ToString("0000000000")+ "," +
                    playlistContainer.Playlists.Count.ToString("0000000000") + "," +
                    playlistContainer.Checksum.ToString("0000000000") + ",0</version>";

            /* Remove the playlists because we need to validate the checksum again. */
            playlistContainer.Playlists.RemoveRange(position, playlists.Count);

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendChangePlaylistContainer(callback, playlistContainer, xml);
            }
            catch(ProtocolException)
            {
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if(confirmation == null)
            {
                return false;
            }

            /* Add the tracks, since operation was successful. */
            playlistContainer.Playlists.InsertRange(position, playlists);

            /* Set new revision. */
            playlistContainer.Revision = confirmation.Revision;

            return true;
        }
        /// <summary>
        /// Remove multiple playlists from a playlist container.
        /// </summary>
        /// <param name="playlistContainer">The playlist container.</param>
        /// <param name="position">The position of the tracks to remove.</param>
        /// <param name="count">The number of track to remove.</param>
        /// <returns></returns>
        public bool PlaylistContainerRemovePlaylists(PlaylistContainer playlistContainer, int position, int count)
        {
            string user      = this.session.StringUsername;
            long   timestamp = DateTime.Now.Ticks;

            /* Create a sublist view (important!). */
            List<Playlist> playlists = new List<Playlist>();
            playlists = playlistContainer.Playlists.GetRange(position, count);

            /* First remove the playlist(s) to calculate the new checksum. */
            playlistContainer.Playlists.RemoveRange(position, count);

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <del>
                            <i>" + position.ToString() + @"</i>
                            <k>" + count.ToString() + @"</k>
                        </del>
                    </ops>
                    <time>" + timestamp.ToString() + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" + (playlistContainer.Revision + 1).ToString("0000000000") + "," +
                    playlistContainer.Playlists.Count.ToString("0000000000") + "," +
                    playlistContainer.Checksum.ToString("0000000000") + ",0</version>";

            /* Add the playlist(s) again, because we need the old checksum for sending. */
            playlistContainer.Playlists.InsertRange(position, playlists);

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try{
                this.protocol.SendChangePlaylistContainer(callback, playlistContainer, xml.ToString());
            }
            catch(ProtocolException){
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if(confirmation == null)
            {
                return false;
            }

            /* Remove the playlist(s), since operation was successful. */
            playlistContainer.Playlists.RemoveRange(position, count);

            /* Set new revision. */
            playlistContainer.Revision = confirmation.Revision;

            return true;
        }
        /// <summary>
        /// Add multiple tracks to a playlist.
        /// </summary>
        /// <param name="playlist">The playlist.</param>
        /// <param name="tracks">A List of tracks to be added.</param>
        /// <param name="position">The target position of the added track.</param>
        /// <returns>true on success and false on failure.</returns>
        public bool PlaylistAddTracks(Playlist playlist, List<Track> tracks, int position)
        {
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;

            /* Check if user is allowed to edit playlist. */
            if (!playlist.IsCollaborative && playlist.Author != user)
            {
                return false;
            }

            /* Add the tracks for new checksum calculation. */
            playlist.Tracks.InsertRange(position, tracks);

            /* Build a comma separated list of tracks and append '01' to every id!. */
            string trackList = "";

            for (int i = 0; i < tracks.Count; i++)
            {
                trackList += ((i > 0) ? "," : "") + tracks[i].Id + "01";
            }

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <add>
                            <i>" + position + @"</i>
                            <items>" + trackList + @"</items>
                        </add>
                    </ops>
                    <time>" + timestamp + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" +
                    (playlist.Revision + 1).ToString("0000000000") + "," +
                    playlist.Tracks.Count.ToString("0000000000") + "," +
                    playlist.Checksum.ToString("0000000000") + "," +
                    (playlist.IsCollaborative ? "1" : "0") + "</version>";

            /* Remove the tracks again. */
            playlist.Tracks.RemoveRange(position, tracks.Count);

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendChangePlaylist(callback, playlist, xml);
            }
            catch (ProtocolException)
            {
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if (confirmation == null)
            {
                return false;
            }

            /* Add the tracks, since operation was successful. */
            playlist.Tracks.InsertRange(position, tracks);

            /* Set new revision and collaborative flag. */
            playlist.Revision = confirmation.Revision;
            playlist.IsCollaborative = confirmation.Collaborative;

            return true;
        }
        /// <summary>
        /// Get stored user playlists.
        /// </summary>
        /// <returns></returns>
        public PlaylistContainer PlaylistContainer()
        {
            /* Create channel callback. */
            ChannelCallback callback = new ChannelCallback();

            /* Send request and parse response. */
            try
            {
                this.protocol.SendPlaylistRequest(callback, null);

                /* Create and return playlist. */
                return XMLPlaylistParser.ParsePlaylistContainer(callback.Get(this.timeout));
            }
            catch(ProtocolException)
            {
                return Sharpotify.Media.PlaylistContainer.Empty;
            }
        }
 /// <summary>
 /// Get an image (e.g. artist portrait or cover) by requesting
 /// it from the server or loading it from the local cache, if
 /// available.
 /// </summary>
 /// <param name="id">Id of the image to get.</param>
 /// <returns>An <see cref="System.Drawing.Image"/> or null if the request failed.</returns>
 public System.Drawing.Image Image(string id)
 {
     byte[] buffer;
     if ((this.cache != null) && this.cache.Contains("image", id))
     {
         buffer = this.cache.Load("image", id);
     }
     else
     {
         ChannelCallback listener = new ChannelCallback();
         try
         {
             this.protocol.SendImageRequest(listener, id);
         }
         catch (ProtocolException)
         {
             return null;
         }
         buffer = listener.Get(this.Timeout);
         if (this.cache != null)
         {
             this.cache.Store("image", id, buffer);
         }
     }
     try
     {
         return new Bitmap(new MemoryStream(buffer));
     }
     catch (Exception)
     {
         return null;
     }
 }
        /// <summary>
        /// Get a playlist.
        /// </summary>
        /// <param name="id">Id of the playlist to load.</param>
        /// <param name="cached">Whether to use a cached version if available or not.</param>
        /// <returns></returns>
        public Playlist Playlist(string id, bool cached)
        {
            /*
             * Check if id is a 32-character hex string,
             * if not try to parse it as a Spotify URI.
             */
            if(id.Length != 32 && !Hex.IsHex(id))
            {
                try
                {
                    Link link = Link.Create(id);

                    if(!link.IsPlaylistLink)
                    {
                        throw new ArgumentException("Given Spotify URI is not a playlist URI.");
                    }

                    id = link.Id;
                }
                catch(InvalidSpotifyURIException)
                {
                    throw new ArgumentException("Given id is neither a 32-character hex string nor a valid Spotify URI.");
                }
            }

            /* Data buffer. */
            byte[] data;

            if(cached && this.cache != null && this.cache.Contains("playlist", id))
            {
                data = this.cache.Load("playlist", id);
            }
            else
            {
                /* Create channel callback */
                ChannelCallback callback = new ChannelCallback();

                /* Send playlist request. */
                try
                {
                    this.protocol.SendPlaylistRequest(callback, id);
                }
                catch(ProtocolException)
                {
                    return null;
                }

                /* Get data. */
                data = callback.Get(this.timeout);

                /* Save data to cache. */
                if(this.cache != null)
                {
                    this.cache.Store("playlist", id, data);
                }
            }

            /* Create and return playlist. */
            return XMLPlaylistParser.ParsePlaylist(data, id);
        }
        /// <summary>
        /// Browse information for multiple tracks by id.
        /// </summary>
        /// <param name="ids">A List of ids identifying the tracks to browse.</param>
        /// <returns></returns>
        public List<Track> BrowseTracks(List<string> ids)
        {
            byte[] data;
            StringBuilder hashBuffer = new StringBuilder();

            for (int i = 0; i < ids.Count; i++)
            {
                string id = ids[i];

                if (id.Length != 32 && !Hex.IsHex(id))
                {
                    try
                    {
                        Link link = Link.Create(id);

                        if (!link.IsTrackLink)
                        {
                            throw new ArgumentException("Browse type doesn't match given Spotify URI.");
                        }

                        id = link.Id;

                        /* Set parsed id in list. */
                        ids[i] = id;
                    }
                    catch (InvalidSpotifyURIException)
                    {
                        throw new ArgumentException("Given id is neither a 32-character hex string nor a valid Spotify URI.");
                    }
                }
                hashBuffer.Append(id);
            }

            string hash = Hex.ToHex(Hash.Sha1(Hex.ToBytes(hashBuffer.ToString())));

            /* Check cache. */
            if ((this.cache != null) && this.cache.Contains("browse", hash))
            {
                data = this.cache.Load("browse", hash);
            }
            else
            {
                /* Create channel callback */
                ChannelCallback listener = new ChannelCallback();

                /* Send browse request. */
                try
                {
                    this.protocol.SendBrowseRequest(listener, 3, ids);
                }
                catch (ProtocolException)
                {
                    return null;
                }

                /* Get data. */
                data = listener.Get(this.Timeout);

                /* Save to cache. */
                if (this.cache != null)
                {
                    this.cache.Store("browse", hash, data);
                }
            }
            return XMLMediaParser.ParseResult(data).Tracks;
        }
        /// <summary>
        /// Remove multiple tracks from a playlist.
        /// </summary>
        /// <param name="playlist">The playlist.</param>
        /// <param name="position">The position of the tracks to remove.</param>
        /// <param name="count">The number of track to remove.</param>
        /// <returns>true on success and false on failure.</returns>
        public bool PlaylistRemoveTracks(Playlist playlist, int position, int count)
        {
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;

            /* Check if user is allowed to edit playlist. */
            if (!playlist.IsCollaborative && playlist.Author != user)
            {
                return false;
            }

            /* Create a sublist view (important!) and clone it by constructing a new ArrayList. */
            List<Track> tracks = new List<Track>();
            tracks.AddRange(playlist.Tracks.GetRange(position, count));

            /* First remove the track(s) to calculate the new checksum. */
            playlist.Tracks.RemoveRange(position, count);

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <del>
                            <i>" + position + @"</i>
                            <k>" + count + @"</k>
                        </del>
                    </ops>
                    <time>" + timestamp + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" +
                    (playlist.Revision + 1).ToString("0000000000") + "," +
                    playlist.Tracks.Count.ToString("0000000000") + "," +
                    playlist.Checksum.ToString("0000000000") + "," +
                    (playlist.IsCollaborative ? "1" : "0") + "</version>";

            /* Add the track(s) again, because we need the old checksum for sending. */
            playlist.Tracks.InsertRange(position, tracks);

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendChangePlaylist(callback, playlist, xml);
            }
            catch (ProtocolException)
            {
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if (confirmation == null)
            {
                return false;
            }

            /* Remove the track(s), since operation was successful. */
            playlist.Tracks.RemoveRange(position, count);

            /* Set new revision and collaborative flag. */
            playlist.Revision = confirmation.Revision;
            playlist.IsCollaborative = confirmation.Collaborative;

            return true;
        }
        /// <summary>
        /// Fetch a toplist.
        /// </summary>
        /// <param name="type">A toplist type. e.g. "artist", "album" or "track".</param>
        /// <param name="region">A region code or null. e.g. "SE" or "DE".</param>
        /// <param name="username">A username or null.</param>
        /// <returns></returns>
        public Result Toplist(ToplistType type, string region, string username)
        {
            /* Create channel callback and parameter map. */
            ChannelCallback listener = new ChannelCallback();
            Dictionary<string, string> paramsarg = new Dictionary<string, string>();
            string str = EnumUtils.GetName(typeof(ToplistType), type).ToLower();

            /* Add parameters. */
            paramsarg.Add("type", str);
            paramsarg.Add("region", region);
            paramsarg.Add("username", username);

            /* Send toplist request. */
            try
            {
                this.protocol.SendToplistRequest(listener, paramsarg);
            }
            catch (ProtocolException)
            {
                return null;
            }
            /* Get data. */
            byte[] data = listener.Get(this.timeout);

            /* Create result from XML. */
            return XMLMediaParser.ParseResult(data);
        }
Example #10
0
        /// <summary>
        /// Browse artist, album or track info.
        /// </summary>
        /// <param name="type">Type of media to browse for.</param>
        /// <param name="id">A 32-character hex string or a Spotify URI.</param>
        /// <returns></returns>
        private object Browse(BrowseType type, string id)
        {
            if (id.Length != 32 && !Hex.IsHex(id))
            {
                try
                {
                    Link link = Link.Create(id);

                    if ((type == BrowseType.ARTIST && !link.IsArtistLink) ||
                       (type == BrowseType.ALBUM && !link.IsAlbumLink) ||
                       (type == BrowseType.TRACK && !link.IsTrackLink))
                    {
                        throw new ArgumentException("Browse type doesn't match given Spotify URI.");
                    }

                    id = link.Id;
                }
                catch (InvalidSpotifyURIException)
                {
                    throw new ArgumentException("Given id is neither a 32-character hex string nor a valid Spotify URI.");
                }
            }

            /* Create channel callback. */
            ChannelCallback listener = new ChannelCallback();

            /* Send browse request. */
            try
            {
                this.protocol.SendBrowseRequest(listener, (int)type, id);
            }
            catch (ProtocolException)
            {
                return null;
            }
            return XMLMediaParser.Parse(listener.Get(this.Timeout));
        }
Example #11
0
 /// <summary>
 /// Search for an artist, album or track.
 /// </summary>
 /// <param name="query">Your search query.</param>
 /// <returns>A <see cref="Result"/> object.</returns>
 public Result Search(string query)
 {
     ChannelCallback listener = new ChannelCallback();
     try
     {
         this.protocol.SendSearchQuery(listener, query);
     }
     catch (ProtocolException)
     {
         return null;
     }
     Result result = (Result)XMLMediaParser.Parse(listener.Get(this.Timeout));
     result.Query = query;
     return result;
 }
Example #12
0
        /// <summary>
        /// Request multiple replacement track.
        /// </summary>
        /// <param name="tracks">The tracks to search the replacements for.</param>
        /// <returns></returns>
        public List<Track> Replacement(List<Track> tracks)
        {
            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send browse request. */
            try{
                this.protocol.SendReplacementRequest(callback, tracks);
            }
            catch(ProtocolException){
                return null;
            }

            /* Get data. */
            byte[] data = callback.Get(this.timeout);

            /* Create result from XML. */
            return XMLMediaParser.ParseResult(data).Tracks;
        }
Example #13
0
        /// <summary>
        /// Set playlist information.
        /// </summary>
        /// <param name="playlist">The <see cref="Playlist"/> to change.</param>
        /// <param name="description">The description to set.</param>
        /// <param name="picture">The picture to set.</param>
        /// <returns>true on success or false on failure.</returns>
        public bool PlaylistSetInformation(Playlist playlist, string description, string picture)
        {
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;

            /* Check if user is allowed to edit playlist. */
            if (playlist.Author != user)
            {
                return false;
            }

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <description>" + description + @"</description>
                        <picture>" + picture + @"</picture>
                    </ops>
                    <time>" + timestamp + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" +
                    (playlist.Revision + 1).ToString("0000000000") + "," +
                    playlist.Tracks.Count.ToString("0000000000") + "," +
                    playlist.Checksum.ToString("0000000000") + "," +
                    (playlist.IsCollaborative ? "1" : "0") + "</version>";

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendChangePlaylist(callback, playlist, xml);
            }
            catch (ProtocolException)
            {
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if (confirmation == null)
            {
                return false;
            }
            /* Set metadata, since the operation was successful. */
            playlist.Description = description;
            playlist.Picture = picture;

            /* Set new revision and collaborative flag. */
            playlist.Revision = confirmation.Revision;
            playlist.IsCollaborative = confirmation.Collaborative;

            return true;
        }
Example #14
0
        /// <summary>
        /// Create a playlist.
        /// <remarks>This just creates a playlist,
        /// but doesn't add it to the playlist container!</remarks>
        /// </summary>
        /// <param name="name">The name of the playlist to create.</param>
        /// <param name="collaborative">If the playlist shall be collaborative.</param>
        /// <param name="description">A description of the playlist.</param>
        /// <param name="picture">An image id to associate with this playlist.</param>
        /// <returns>A <see cref="Playlist"/> object or null on failure.</returns>
        public Playlist PlaylistCreate(string name, bool collaborative, string description, string picture)
        {
            string id = Hex.ToHex(RandomBytes.GetRandomBytes(16));
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;
            Playlist playlist = new Playlist(id, name, user, collaborative);

            /* Create XML builder. */
            string xml = @"
                <id-is-unique/>
                <change>
                    <ops>
                        <create/>
                        <name>" + name + @"</name>
                        <description>" + description + @"</description>
                        <picture>" + picture + @"</picture>
                    </ops>
                    <time>" + timestamp + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>0000000001,0000000000,0000000001," + ((collaborative) ? "1" : "0") + "</version>";

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendCreatePlaylist(callback, playlist, xml);
            }
            catch (ProtocolException)
            {
                return null;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if (confirmation == null)
            {
                return null;
            }

            /* Set new revision and collaborative flag. */
            playlist.Revision = confirmation.Revision;
            playlist.IsCollaborative = confirmation.Collaborative;

            return playlist;
        }
Example #15
0
 public MusicStream GetMusicStream(Track track, Sharpotify.Media.File file, TimeSpan timeout)
 {
     ChannelCallback listener = new ChannelCallback();
     ChannelHeaderCallback callback2 = new ChannelHeaderCallback();
     try
     {
         this.protocol.SendPlayRequest();
     }
     catch (ProtocolException)
     {
         return null;
     }
     try
     {
         this.protocol.SendAesKeyRequest(listener, track, file);
     }
     catch (ProtocolException)
     {
         return null;
     }
     byte[] key = listener.Get(timeout);
     MusicStream output = new MusicStream();
     try
     {
         ChannelStreamer streamer = new ChannelStreamer(this.protocol, file, key, output);
     }
     catch (Exception)
     {
         /* Ignore */
     }
     return output;
 }
Example #16
0
        /// <summary>
        /// Destroy a playlist.
        /// </summary>
        /// <param name="playlist">The playlist to destroy.</param>
        /// <returns>true if the playlist was successfully destroyed, false otherwise.</returns>
        public bool PlaylistDestroy(Playlist playlist)
        {
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <destroy/>
                    </ops>
                    <time>" + timestamp + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" +
                    (playlist.Revision + 1).ToString("0000000000") + "," +
                    playlist.Tracks.Count.ToString("0000000000") + "," +
                    playlist.Checksum.ToString("0000000000")+ "," +
                    (playlist.IsCollaborative ? "1" : "0") + "</version>";

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendChangePlaylist(callback, playlist, xml);
            }
            catch (ProtocolException)
            {
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if (confirmation == null)
            {
                return false;
            }

            return true;
        }
Example #17
0
        /// <summary>
        /// Move track position into a play list.
        /// </summary>
        /// <param name="playlist">The playlist.</param>
        /// <param name="sourcePosition">The source Track position.</param>
        /// <param name="destPosition">The destiny Track position.</param>
        /// <returns>true on success and false on failure.</returns>
        public bool PlaylistMoveTrack(Playlist playlist, int sourcePosition, int destPosition)
        {
            string user = this.session.StringUsername;
            long timestamp = DateTime.Now.Ticks;

            /* Check if user is allowed to edit playlist. */
            if (!playlist.IsCollaborative && playlist.Author != user)
            {
                return false;
            }

            /* Save the original tracks positions*/
            List<Track> tracks = new List<Track>();
            tracks.AddRange(playlist.Tracks);

            /* First move the track(s) to calculate the new checksum. */
            /* 1. copy the track in the destiny position */
            playlist.Tracks.Insert(destPosition, playlist.Tracks[sourcePosition]);
            /* 2. remove the track in the source position */
            playlist.Tracks.RemoveAt(sourcePosition);

            /* Create XML builder. */
            string xml = @"
                <change>
                    <ops>
                        <mov>
                            <i>" + sourcePosition + @"</i>
                            <j>" + destPosition + @"</j>
                        </mov>
                    </ops>
                    <time>" + timestamp + @"</time>
                    <user>" + user + @"</user>
                </change>
                <version>" +
                    (playlist.Revision + 1).ToString("0000000000") + "," +
                    playlist.Tracks.Count.ToString("0000000000") + "," +
                    playlist.Checksum.ToString("0000000000") + "," +
                    (playlist.IsCollaborative ? "1" : "0") + "</version>";

            /* Set old track list to recalculate the old checksum */
            playlist.Tracks = tracks;

            /* Create channel callback */
            ChannelCallback callback = new ChannelCallback();

            /* Send change playlist request. */
            try
            {
                this.protocol.SendChangePlaylist(callback, playlist, xml);
            }
            catch (ProtocolException)
            {
                return false;
            }

            /* Get response. */
            byte[] data = callback.Get(this.timeout);

            /* Check confirmation. */
            PlaylistConfirmation confirmation = XMLPlaylistParser.ParsePlaylistConfirmation(data);

            if (confirmation == null)
            {
                return false;
            }

            /* Move the track(s), since operation was successful. */
            /* 1. copy the track in the destiny position */
            playlist.Tracks.Insert(destPosition, playlist.Tracks[sourcePosition]);
            /* 2. remove the track in the source position */
            playlist.Tracks.RemoveAt(sourcePosition);

            /* Set new revision and collaborative flag. */
            playlist.Revision = confirmation.Revision;
            playlist.IsCollaborative = confirmation.Collaborative;

            return true;
        }