protected async Task <bool> GetLibraryUpdateAsync(CancellationToken cancellationToken)
        {
            DACPRequest request = new DACPRequest("/update");

            request.QueryParameters["revision-number"]    = CurrentLibraryUpdateNumber.ToString();
            request.QueryParameters["daap-no-disconnect"] = "1";

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                var nodes = DACPNodeDictionary.Parse(response.Nodes);
                CurrentLibraryUpdateNumber = nodes.GetInt("musr");
            }
            catch
            {
                return(false);
            }
            return(true);
        }
        protected async Task <bool> UpdateCurrentSongUserRatingAsync()
        {
            // Make sure we have all the values we need
            if (CurrentDatabaseID == 0 || CurrentContainerID == 0 || CurrentItemID == 0 || CurrentAlbumPersistentID == 0)
            {
                ClearCurrentSongUserRating();
                return(true);
            }

            // Make sure this is for the main DB
            if (!ShowUserRating || MainDatabase == null || CurrentDatabaseID != MainDatabase.ID)
            {
                ClearCurrentSongUserRating();
                return(true);
            }

            // If we're requesting the rating for a new song, clear out the old value
            if (CurrentItemID != _ratingUpdatedForSongID)
            {
                ClearCurrentSongUserRating();
            }

            DACPRequest request = new DACPRequest("/databases/{0}/containers/{1}/items", CurrentDatabaseID, CurrentContainerID);

            request.QueryParameters["meta"] = "dmap.itemid,dmap.containeritemid,daap.songuserrating";
            request.QueryParameters["type"] = "music";
            request.QueryParameters["sort"] = "album";
            var query = DACPQueryCollection.And(DACPQueryPredicate.Is("daap.songalbumid", CurrentAlbumPersistentID), DACPQueryPredicate.Is("dmap.itemid", CurrentItemID));

            request.QueryParameters["query"] = query.ToString();

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                var mlcl = response.Nodes.First(n => n.Key == "mlcl");

                var songNodes = DACPUtility.GetResponseNodes(mlcl.Value);
                foreach (var songData in songNodes)
                {
                    var nodes = DACPNodeDictionary.Parse(songData.Value);
                    var id    = nodes.GetInt("miid");
                    if (id != CurrentItemID)
                    {
                        continue;
                    }
                    var rating = nodes.GetByte("asur");
                    SetCurrentSongUserRatingFromServer(rating);
                    break;
                }
            }
            catch
            {
                ClearCurrentSongUserRating();
                return(false);
            }
            return(true);
        }
Пример #3
0
        public static IDACPList GetAlphaGroupedDACPList <T>(List <T> items, IEnumerable <DACPNode> headers, bool useGroupMinimums = true)
        {
            // Determine whether we need to process the headers
            bool processHeaders = false;

            // Make sure headers were returned
            if (headers != null)
            {
                if (useGroupMinimums)
                {
                    // Only show headers if we have at least 10 items
                    if (items.Count >= 10)
                    {
                        // Also make sure we have more than 1 group
                        headers = headers.ToList();
                        if (((IList)headers).Count >= 2)
                        {
                            processHeaders = true;
                        }
                    }
                }
                else
                {
                    processHeaders = true;
                }
            }

            // If we're not processing headers, just return the items in a list
            if (!processHeaders)
            {
                return(items.ToDACPList());
            }

            var result = new DACPList <ItemGroup <T> >(true);

            // Create groups for each letter
            var groupsByChar = new Dictionary <char, ItemGroup <T> >(AlphaGroupChars.Length);

            foreach (char c in AlphaGroupChars)
            {
                var group = new ItemGroup <T>(c.ToString());
                result.Add(group);
                groupsByChar[c] = group;
            }

            // Add the items to their groups
            foreach (var header in headers.Select(n => DACPNodeDictionary.Parse(n.Value)))
            {
                char headerChar = GetKeyChar(header.GetString("mshc"));
                int  skip       = header.GetInt("mshi");
                int  take       = header.GetInt("mshn");

                groupsByChar[headerChar].AddRange(items.Skip(skip).Take(take));
            }

            return(result);
        }
Пример #4
0
        public static DACPNodeDictionary Parse(IEnumerable <DACPNode> nodes)
        {
            DACPNodeDictionary dictionary = new DACPNodeDictionary();

            foreach (var node in nodes)
            {
                dictionary[node.Key] = node.Value;
            }

            return(dictionary);
        }
        protected async Task <bool> GetServerInfoAsync()
        {
            DACPRequest request = new DACPRequest("/server-info");

            request.IncludeSessionID = false;

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                // Process response
                ServerVersionString = response.HTTPResponse.Headers.GetValues("DAAP-Server").FirstOrDefault();

                var nodes = DACPNodeDictionary.Parse(response.Nodes);

                // Fixing an issue with Apple TV devices where \0 may be appended to the end of the library name
                string libraryName = nodes.GetString("minm");
                if (!string.IsNullOrEmpty(libraryName))
                {
                    libraryName = libraryName.Replace("\0", "");
                }
                LibraryName = libraryName;

                ServerVersion     = nodes.GetInt("aeSV");
                ServerDMAPVersion = nodes.GetInt("mpro");
                ServerDAAPVersion = nodes.GetInt("apro");

                // MAC addresses
                if (nodes.ContainsKey("msml"))
                {
                    List <string> macAddresses = new List <string>();
                    var           addressNodes = DACPUtility.GetResponseNodes(nodes["msml"]).Where(n => n.Key == "msma").Select(n => n.Value);
                    foreach (var addressNode in addressNodes)
                    {
                        var address = BitConverter.ToInt64(addressNode, 0);
                        address = address >> 16;
                        macAddresses.Add(address.ToString("X12"));
                    }
                    MACAddresses = macAddresses.ToArray();
                }
            }
            catch (Exception e)
            {
                HandleHTTPException(request, e);
                return(false);
            }

            return(true);
        }
        protected async Task <bool> GetServerCapabilitiesAsync()
        {
            DACPRequest request = new DACPRequest("/ctrl-int");

            request.IncludeSessionID = false;

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                // Process response
                var mlcl  = DACPUtility.GetResponseNodes(response.Nodes.First(n => n.Key == "mlcl").Value);
                var nodes = DACPNodeDictionary.Parse(mlcl.First(n => n.Key == "mlit").Value);

                if (nodes.ContainsKey("ceSX"))
                {
                    Int64 ceSX = nodes.GetLong("ceSX");

                    // Bit 0: Supports Play Queue
                    if ((ceSX & (1 << 0)) != 0)
                    {
                        SupportsPlayQueue = true;
                    }

                    // Bit 1: iTunes Radio? Appeared in iTunes 11.1.2 with the iTunes Radio DB.
                    // Apple's Remote for iOS doesn't seem to use this bit to determine whether iTunes Radio is available.
                    // Instead, it looks for an iTunes Radio database and checks whether it has any containers.

                    // Bit 2: Genius Shuffle Enabled/Available
                    if ((ceSX & (1 << 2)) != 0)
                    {
                        SupportsGeniusShuffle = true;
                    }
                }

                // Apple TV
                // TODO: Is this the best way to detect this?
                IsAppleTV = nodes.GetBool("ceDR");
            }
            catch (Exception e)
            {
                HandleHTTPException(request, e);
                return(false);
            }

            return(true);
        }
        protected async Task <bool> UpdateCurrentVolumeLevelAsync()
        {
            DACPRequest request = new DACPRequest("/ctrl-int/1/getproperty");

            request.QueryParameters["properties"] = "dmcp.volume";

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                var nodes = DACPNodeDictionary.Parse(response.Nodes);

                CurrentVolume = (byte)nodes.GetInt("cmvo");
            }
            catch { return(false); }
            return(true);
        }
        protected async Task <bool> UpdateTrackTimeAsync()
        {
            DACPRequest request = new DACPRequest("/ctrl-int/1/getproperty");

            request.QueryParameters["properties"] = "dacp.playingtime";

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                var nodes = DACPNodeDictionary.Parse(response.Nodes);

                TrackTimeTotal     = nodes.GetInt("cast");
                TrackTimeRemaining = nodes.GetNullableInt("cant") ?? TrackTimeTotal;
            }
            catch { return(false); }
            return(true);
        }
        protected async Task <ConnectionResult> LoginAsync()
        {
            DACPRequest request = new DACPRequest("/login");

            request.QueryParameters["pairing-guid"] = "0x" + PairingCode;
            request.IncludeSessionID = false;

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                // Process response
                var nodes = DACPNodeDictionary.Parse(response.Nodes);

                if (!nodes.ContainsKey("mlid"))
                {
                    return(ConnectionResult.InvalidPIN);
                }

                SessionID = nodes.GetInt("mlid");
            }
            catch (DACPRequestException e)
            {
                int statusCode = (int)e.Response.StatusCode;
                if (statusCode >= 500 && statusCode <= 599)
                {
                    return(ConnectionResult.InvalidPIN);
                }
                return(ConnectionResult.ConnectionError);
            }
            catch (Exception e)
            {
                HandleHTTPException(request, e);
                return(ConnectionResult.ConnectionError);
            }

            return(ConnectionResult.Success);
        }
Пример #10
0
 public static IDACPList GetAlphaGroupedDACPList <T>(IEnumerable <DACPNode> nodes, Func <DACPNodeDictionary, T> itemGenerator, out List <T> items, string listKey = DefaultListKey, bool useGroupMinimums = true)
 {
     return(GetAlphaGroupedDACPList(nodes, d => itemGenerator(DACPNodeDictionary.Parse(d)), out items, listKey, useGroupMinimums));
 }
Пример #11
0
        protected async Task <bool> GetControlPromptUpdateAsync(CancellationToken cancellationToken)
        {
            DACPRequest request = new DACPRequest("/controlpromptupdate");

            request.QueryParameters["prompt-id"] = _controlPromptUpdateNumber.ToString();

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                _controlPromptUpdateNumber = response.Nodes.First(n => n.Key == "miid").Value.GetInt32Value();

                // Parse response
                // This comes back as a list of string key/value pairs.
                var nodeDictionary = response.Nodes.Where(n => n.Key == "mdcl").Select(n => DACPNodeDictionary.Parse(n.Value)).ToDictionary(n => n.GetString("cmce"), n => n.GetString("cmcv"));

#if DEBUG
                if (_log.EffectiveLevel <= LogLevel.Trace)
                {
                    string logMessage = "ControlPromptUpdate Response:\r\n";
                    foreach (var kvp in nodeDictionary)
                    {
                        logMessage += string.Format("{0}: {1}\r\n", kvp.Key, kvp.Value);
                    }
                    _log.Trace(logMessage);
                }
#endif

                if (nodeDictionary.ContainsKey("kKeybMsgKey_MessageType"))
                {
                    switch (nodeDictionary["kKeybMsgKey_MessageType"])
                    {
                    case "0":     // Show keyboard
                        CurrentAppleTVKeyboardTitle           = nodeDictionary.GetValueOrDefault("kKeybMsgKey_Title");
                        CurrentAppleTVKeyboardSubText         = nodeDictionary.GetValueOrDefault("kKeybMsgKey_SubText");
                        CurrentAppleTVKeyboardString          = nodeDictionary.GetValueOrDefault("kKeybMsgKey_String");
                        _appleTVKeyboardSessionID             = nodeDictionary["kKeybMsgKey_SessionID"];
                        _appleTVKeyboardSecureText            = (nodeDictionary["kKeybMsgKey_SecureText"] == "1");
                        _appleTVKeyboardSecureTextCertificate = nodeDictionary.GetValueOrDefault("certificate");
                        _appleTVKeyboardSecureTextChallenge   = nodeDictionary.GetValueOrDefault("challenge");

                        if (_appleTVKeyboardSecureText)
                        {
                            CurrentAppleTVKeyboardType = AppleTVKeyboardType.Password;
                        }
                        else
                        {
                            CurrentAppleTVKeyboardType = (AppleTVKeyboardType)int.Parse(nodeDictionary["kKeybMsgKey_KeyboardType"]);
                        }
                        IsAppleTVKeyboardVisible = true;
                        break;

                    case "2":     // Hide keyboard
                        IsAppleTVKeyboardVisible = false;
                        break;

                    case "5":     // Trackpad interface update
                        _appleTVTrackpadPort = int.Parse(nodeDictionary["kKeybMsgKey_String"]) ^ AppleTVEncryptionKey;
                        _appleTVTrackpadKey  = BitUtility.NetworkToHostOrder(int.Parse(nodeDictionary["kKeybMsgKey_SubText"]) ^ AppleTVEncryptionKey);
                        _log.Info("Apple TV virtual trackpad parameters updated: Encryption key: {0:X8} Port: {1}", _appleTVTrackpadKey, _appleTVTrackpadPort);
                        break;
                    }
                }
            }
            catch
            {
                return(false);
            }
            return(true);
        }
Пример #12
0
 protected virtual void ProcessNodes(DACPNodeDictionary nodes)
 {
     ID           = nodes.GetInt("miid");
     PersistentID = (UInt64)nodes.GetLong("mper");
     Name         = nodes.GetString("minm");
 }
Пример #13
0
 internal Task <IDACPList> GetAlphaGroupedListAsync <T>(DACPRequest request, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DACPUtility.DefaultListKey)
 {
     return(GetAlphaGroupedListAsync(request, b => itemGenerator(DACPNodeDictionary.Parse(b)), listKey));
 }
Пример #14
0
 public static IEnumerable <T> GetItemsFromNodes <T>(byte[] data, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DefaultListKey)
 {
     return(GetItemsFromNodes(GetResponseNodes(data), d => itemGenerator(DACPNodeDictionary.Parse(d)), listKey));
 }
Пример #15
0
        protected async Task <bool> GetPlayStatusUpdateAsync(CancellationToken cancellationToken)
        {
            // Do not pass the cancellation token to the HTTP request since cancelling a request will cause iTunes to close the current session.
            DACPRequest request = new DACPRequest("/ctrl-int/1/playstatusupdate");

            request.QueryParameters["revision-number"] = _playStatusRevisionNumber.ToString();

            try
            {
                var response = await SubmitRequestAsync(request).ConfigureAwait(false);

                // Do we still need to process this response?
                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                // Process response
                ThreadUtility.RunOnUIThread(() =>
                {
                    timerTrackTimeUpdate.Stop();
                });

                var nodes = DACPNodeDictionary.Parse(response.Nodes);
                _playStatusRevisionNumber = nodes.GetInt("cmsr");

                int oldSongID = CurrentItemID;

                if (nodes.ContainsKey("canp"))
                {
                    // Current song and container IDs
                    byte[] value = nodes["canp"];

                    byte[] dbID            = { value[0], value[1], value[2], value[3] };
                    byte[] containerID     = { value[4], value[5], value[6], value[7] };
                    byte[] containerItemID = { value[8], value[9], value[10], value[11] };
                    byte[] itemID          = { value[12], value[13], value[14], value[15] };
                    CurrentDatabaseID      = dbID.GetInt32Value();
                    CurrentContainerID     = containerID.GetInt32Value();
                    CurrentContainerItemID = containerItemID.GetInt32Value();
                    CurrentItemID          = itemID.GetInt32Value();
                }
                else
                {
                    CurrentDatabaseID      = 0;
                    CurrentContainerID     = 0;
                    CurrentContainerItemID = 0;
                    CurrentItemID          = 0;
                }
                CurrentSongName          = nodes.GetString("cann");
                CurrentArtist            = nodes.GetString("cana");
                CurrentAlbum             = nodes.GetString("canl");
                CurrentAlbumPersistentID = (UInt64)nodes.GetLong("asai");
                PlayState = (PlayStates)nodes.GetByte("caps");

                // Shuffle
                int caas = nodes.GetInt("caas");
                IsShuffleAvailable = (caas & (1 << 1)) != 0;
                ShuffleState       = nodes.GetBool("cash");

                // Repeat
                int caar = nodes.GetInt("caar");
                IsRepeatOneAvailable = (caar & (1 << 1)) != 0;
                IsRepeatAllAvailable = (caar & (1 << 2)) != 0;
                RepeatState          = (RepeatStates)nodes.GetByte("carp");

                CurrentMediaKind = nodes.GetInt("cmmk");
                ShowUserRating   = nodes.GetBool("casu");

                // Track length (ms)
                TrackTimeTotal = nodes.GetInt("cast");
                // Remaining track length (ms)
                TrackTimeRemaining = nodes.GetNullableInt("cant") ?? TrackTimeTotal;

                // dacp.visualizer
                VisualizerActive = nodes.GetBool("cavs");
                // dacp.visualizerenabled
                VisualizerAvailable = nodes.GetBool("cave");
                // dacp.fullscreen
                FullScreenModeActive = nodes.GetBool("cafs");
                // dacp.fullscreenenabled
                FullScreenModeAvailable = nodes.GetBool("cafe");

                // iTunes Radio
                if (iTunesRadioDatabase != null && iTunesRadioDatabase.ID == CurrentDatabaseID)
                {
                    IsCurrentlyPlayingiTunesRadio = true;
                    CurrentiTunesRadioStationName = nodes.GetString("ceNR");

                    // caks = 1 when the next button is disabled, and 2 when it's enabled
                    IsiTunesRadioNextButtonEnabled = (nodes.GetByte("caks") == 2);

                    // "aelb" indicates whether the star button (iTunes Radio menu) should be enabled, but this only seems to be set to true
                    // when connected via Home Sharing. This parameter is missing when an ad is playing, so use this to determine whether
                    // the menu should be enabled.
                    IsiTunesRadioMenuEnabled = nodes.ContainsKey("aelb");

                    IsiTunesRadioSongFavorited = (nodes.GetByte("aels") == 2);
                }
                else
                {
                    IsCurrentlyPlayingiTunesRadio = false;
                }


                if (IsCurrentlyPlayingiTunesRadio)
                {
                    var caks = nodes.GetByte("caks");
                    IsiTunesRadioNextButtonEnabled = !(caks == 1);
                }

                if (!nodes.ContainsKey("casc") || nodes.GetBool("casc") == true)
                {
                    IsPlayPositionBarEnabled = true;
                }
                else
                {
                    IsPlayPositionBarEnabled = false;
                }

                // Genius Shuffle
                IsCurrentlyPlayingGeniusShuffle = nodes.GetBool("ceGs");
                // There are two other nodes related to Genius Shuffle, "ceGS" and "aeGs" (currently unknown)

                // If the song ID changed, refresh the album art
                if (oldSongID != CurrentItemID)
                {
                    PropertyChanged.RaiseOnUIThread(this, "CurrentAlbumArtURL");
                }

                ThreadUtility.RunOnUIThread(() =>
                {
                    if (PlayState == PlayStates.Playing)
                    {
                        timerTrackTimeUpdate.Start();
                    }
                    else if (PlayState == PlayStates.FastForward || PlayState == PlayStates.Rewind)
                    {
                        BeginRepeatedTrackTimeRequest();
                    }
                });

                var volumeTask     = UpdateCurrentVolumeLevelAsync();
                var userRatingTask = UpdateCurrentSongUserRatingAsync();
                var playQueueTask  = UpdatePlayQueueContentsAsync();

                Task[] tasks = new[] { volumeTask, userRatingTask, playQueueTask };

#if WP7
                await TaskEx.WhenAll(tasks).ConfigureAwait(false);
#else
                await Task.WhenAll(tasks).ConfigureAwait(false);
#endif

                SubmitGetSpeakersRequest();
            }
            catch { return(false); }
            return(true);
        }
Пример #16
0
 public static IEnumerable <T> GetItemsFromNodes <T>(IEnumerable <DACPNode> nodes, Func <DACPNodeDictionary, T> itemGenerator, string listKey = DefaultListKey)
 {
     return(GetItemsFromNodes(nodes, d => itemGenerator(DACPNodeDictionary.Parse(d)), listKey));
 }
Пример #17
0
 public DACPElement(DACPServer server, DACPNodeDictionary nodes)
 {
     Server = server;
     ProcessNodes(nodes);
 }