/Application/Core/ServiceManager.cs
http://yet-another-music-application.googlecode.com/ · C# · 1416 lines · 1031 code · 136 blank · 249 comment · 169 complexity · 64318425198c85b7792c7a577a0de005 MD5 · raw file
- /**
- * ServiceManager.cs
- *
- * Communicates with the Stoffi Services.
- *
- * * * * * * * * *
- *
- * Copyright 2012 Simplare
- *
- * This code is part of the Stoffi Music Player Project.
- * Visit our website at: stoffiplayer.com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 3 of the License, or (at your option) any later version.
- *
- * See stoffiplayer.com/license for more information.
- **/
-
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Collections.Specialized;
- using System.IO;
- using System.Linq;
- using System.Globalization;
- using System.Runtime.InteropServices;
- using System.Net;
- using System.Text;
- using System.Threading;
- using System.Xml;
- using System.Web;
- using Newtonsoft.Json.Linq;
-
- namespace Stoffi
- {
- /// <summary>
- /// Represents a manager that handles communication with
- /// the Stoffi services.
- /// </summary>
- public static class ServiceManager
- {
- #region Members
-
- private static OAuth.Manager oauth = new OAuth.Manager();
- private static string oauth_request_token = "";
- private static string oauth_key = "baAito0V8WXtdpfjrE4GfUhld4IvFMd9Ud5EYw8i";
- private static string oauth_secret = "cU1esKuX0VruYYVhU5Mrry4SukK5yL9uHcYoHip1";
- private static string domain = "http://alpha.stoffiplayer.com";
- private static string deviceName = "";
- private static Dictionary<string, string> toBeSynced = new Dictionary<string,string>();
- private static Timer syncDelay = null;
- private static int userID = -1;
- private static string lang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
-
- #endregion
-
- #region Properties
-
- /// <summary>
- /// Gets whether the client is linked to a service account or not
- /// </summary>
- public static bool Linked
- {
- get
- {
- return SettingsManager.OAuthToken != null &&
- SettingsManager.OAuthToken != "" &&
- SettingsManager.OAuthSecret != null &&
- SettingsManager.OAuthSecret != "";
- }
- }
-
- /// <summary>
- /// Gets the URL for authorizing the application with OAuth
- /// </summary>
- public static string RequestURL
- {
- get
- {
- return String.Format("{0}/{1}/oauth/authorize?oauth_token={2}",
- domain,
- lang,
- oauth_request_token);
- }
- }
-
- /// <summary>
- /// Gets the URL for logging out
- /// </summary>
- public static string LogoutURL { get { return String.Format("{0}/{1}/logout", domain, lang); } }
-
- /// <summary>
- /// Gets the Identity representing the currently linked user.
- /// </summary>
- public static CloudIdentity Identity
- {
- get
- {
- return SettingsManager.GetCloudIdentity(userID);
- }
- }
-
- /// <summary>
- /// Gets the callback URL for OAuth
- /// </summary>
- public static string CallbackURL
- {
- get { return String.Format("{0}/controls", domain); }
- }
-
- /// <summary>
- /// Gets the callback URL with parameters to authenticate
- /// </summary>
- public static string CallbackURLWithAuthParams
- {
- get
- {
- return String.Format("{0}/{1}/controls?callback=stoffi&oauth_token={2}&oauth_secret_token={3}",
- domain,
- SettingsManager.Culture.TwoLetterISOLanguageName,
- SettingsManager.OAuthToken,
- SettingsManager.OAuthSecret);
- }
- }
-
- /// <summary>
- /// Gets or sets the name of the device
- /// </summary>
- public static string DeviceName
- {
- get { return deviceName; }
- set
- {
- object old = deviceName;
- deviceName = value;
- DispatchPropertyChanged("DeviceName", old, value, true);
-
- if (old != value as object)
- {
- U.L(LogLevel.Debug, "SERVICE", "Changing device name");
- string url = String.Format("/devices/{0}.json", Identity.DeviceID);
- string query = String.Format("?device[name]={0}&device[version]={1}",
- OAuth.Manager.UrlEncode(value),
- OAuth.Manager.UrlEncode(SettingsManager.Release));
- var response = SendRequest(url, "PUT", query);
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem changing device name");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- U.L(LogLevel.Debug, "SERVICE", "Device name changed successfully");
- }
- if (response != null) response.Close();
- }
- }
- }
-
- /// <summary>
- /// Gets the synchronization profiles.
- /// </summary>
- public static Dictionary<int, string> SyncProfiles { get; private set; }
-
- #endregion
-
- /// <summary>
- /// Creates an instance of the ServiceManager class.
- /// </summary>
- static ServiceManager()
- {
- SyncProfiles = new Dictionary<int, string>();
- }
-
- #region Methods
-
- #region Public
-
- /// <summary>
- /// Initializes the manager.
- /// </summary>
- public static void Initialize()
- {
- ServicePointManager.DefaultConnectionLimit = 1000;
-
- ThreadStart initThread = delegate()
- {
- InitOAuth();
- SyncProfiles = new Dictionary<int, string>();
- SettingsManager.PropertyChanged += SettingsManager_PropertyChanged;
- DispatchInitialized();
- };
- Thread init_thread = new Thread(initThread);
- init_thread.Name = "Service Initializer thread";
- init_thread.Priority = ThreadPriority.Lowest;
- init_thread.Start();
- }
-
- /// <summary>
- /// Shares a track.
- /// </summary>
- /// <param name="track">The track to share</param>
- public static void ShareSong(TrackData track)
- {
- ThreadStart shareThread = delegate()
- {
- string query = String.Format("?title={0}&artist={1}&path={2}&object={3}",
- OAuth.Manager.UrlEncode(track.Title),
- OAuth.Manager.UrlEncode(track.Artist),
- OAuth.Manager.UrlEncode(track.Path),
- "song");
- var response = SendRequest("/shares.json", "POST", query);
-
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE" ,"There was a problem sharing song");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- U.L(LogLevel.Information, "SERVICE", "Shared song successfully");
- }
- if (response != null) response.Close();
- };
- Thread sharethread = new Thread(shareThread);
- sharethread.Name = "Share thread";
- sharethread.Priority = ThreadPriority.Lowest;
- sharethread.Start();
- }
-
- /// <summary>
- /// Shares a playlist.
- /// </summary>
- /// <param name="pl">The playlist to share</param>
- public static void SharePlaylist(PlaylistData pl)
- {
- ThreadStart shareThread = delegate()
- {
- //string query = String.Format("?title={0}&artist={1}&path={2}&type={3}",
- // OAuth.Manager.UrlEncode(track.Title),
- // OAuth.Manager.UrlEncode(track.Artist),
- // OAuth.Manager.UrlEncode(track.Path),
- // "song");
- //var response = SendRequest("/share.json", "POST", query);
-
- //if (response == null || response.StatusCode != HttpStatusCode.OK)
- //{
- // U.L(LogLevel.Error, "SERVICE", "There was a problem sharing song");
- // U.L(LogLevel.Error, "SERVICE", response);
- //}
- //else
- //{
- // U.L(LogLevel.Information, "SERVICE", "Shared playlist successfully");
- //}
- //if (response != null) response.Close();
- };
- Thread sharethread = new Thread(shareThread);
- sharethread.Name = "Share thread";
- sharethread.Priority = ThreadPriority.Lowest;
- sharethread.Start();
- }
-
- /// <summary>
- /// Submits that a track was listened to.
- /// </summary>
- /// <param name="track">The track that was listened to</param>
- /// <param name="playlist">The playlist from which the song was played</param>
- public static void Listen(TrackData track, PlaylistData playlist = null)
- {
- if (!SettingsManager.SubmitSongs) return;
-
- ThreadStart listenThread = delegate()
- {
- string query = String.Format("?title={0}&artist={1}&path={2}&playlist={3}",
- OAuth.Manager.UrlEncode(track.Title),
- OAuth.Manager.UrlEncode(track.Artist),
- OAuth.Manager.UrlEncode(track.Path),
- playlist == null ? "" : playlist.Name);
- var response = SendRequest("/listens.json", "POST", query);
-
- if (response == null || response.StatusCode != HttpStatusCode.Created)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem submitting song");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- U.L(LogLevel.Information, "SERVICE", "Submitted song successfully");
- }
- if (response != null) response.Close();
- };
- Thread l_thread = new Thread(listenThread);
- l_thread.Name = "Submission thread";
- l_thread.Priority = ThreadPriority.Lowest;
- l_thread.Start();
- }
-
- /// <summary>
- /// Link Stoffi to an account
- /// </summary>
- /// <param name="url">The full callback URL (including parameters) where OAuth sent us after authorization</param>
- public static void Link(string url)
- {
- U.L(LogLevel.Information, "SERVICE", "Linking to service account");
- Dictionary<string,string> p = U.GetParams(U.GetQuery(url));
- string token = p["oauth_token"];
- string verifier = p["oauth_verifier"];
- var access_url = domain + "/oauth/access_token";
- try
- {
- OAuth.OAuthResponse accessToken = oauth.AcquireAccessToken(access_url, "POST", verifier);
- string[] tokens = accessToken.AllText.Split('&');
- SettingsManager.OAuthToken = tokens[0].Split('=')[1];
- SettingsManager.OAuthSecret = tokens[1].Split('=')[1];
- U.L(LogLevel.Information, "SERVICE", "Service account has been linked");
- RetrieveUserData();
- }
- catch (Exception e)
- {
- U.L(LogLevel.Warning, "SERVICE", "Problem linking with account: " + e.Message);
- DispatchDisconnected();
- }
- }
-
- /// <summary>
- /// Remove the current link if there is one
- /// </summary>
- public static void Delink()
- {
- if (!Linked) return;
- SettingsManager.OAuthToken = null;
- SettingsManager.OAuthSecret = null;
- try
- {
- InitOAuth();
- DispatchPropertyChanged("Linked", true, false);
- }
- catch (Exception e)
- {
- DispatchDisconnected();
- }
- }
-
- /// <summary>
- /// Update the values of an object.
- /// </summary>
- /// <remarks>
- /// Use this when an object is changed from outside the application (ie async call from server)
- /// in order to prevent the manager to send the update back to the server.
- /// </remarks>
- /// <param name="objectType">The name of the type of object</param>
- /// <param name="objectID">The object ID</param>
- /// <param name="updatedProperties">The updated properties of the object encoded in JSON</param>
- public static void UpdateObject(string objectType, int objectID, string updatedProperties)
- {
- JObject o = JObject.Parse(updatedProperties);
- switch (objectType)
- {
- case "device":
- if (objectID == Identity.ConfigurationID)
- {
- if (o["name"] != null)
- {
- string name = U.UnescapeHTML((string)o["name"]);
- deviceName = name;
- DeviceName = name;
- }
- }
- break;
-
- case "configuration":
- if (objectID == Identity.ConfigurationID)
- SyncProfileUpdated(o);
- else
- {
- object old = SyncProfiles;
- string name = (string)o["name"];
- if (SyncProfiles.ContainsKey(objectID))
- SyncProfiles[objectID] = name;
- else
- SyncProfiles.Add(objectID, name);
- DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
- }
- break;
-
- case "list_config":
- break;
-
- case "column":
- break;
-
- case "column_sort":
- break;
-
- case "equalizer_profile":
- break;
-
- case "keyboard_shortcut_profile":
- break;
-
- case "keyboard_shortcut":
- break;
-
- case "song":
- break;
-
- case "album":
- break;
-
- case "artist":
- break;
-
- case "user":
- break;
- }
- }
-
- /// <summary>
- /// Creates a new object.
- /// </summary>
- /// <remarks>
- /// Use this when an object is changed from outside the application (ie async call from server)
- /// in order to prevent the manager to send the update back to the server.
- /// </remarks>
- /// <param name="objectType">The type of the object</param>
- /// <param name="objectJSON">The object encoded in JSON</param>
- public static void CreateObject(string objectType, string objectJSON)
- {
- JObject o = JObject.Parse(objectJSON);
- switch (objectType)
- {
- case "device":
- break;
-
- case "configuration":
- object old = SyncProfiles;
- int id = Convert.ToInt32((string)o["id"]);
- string name = (string)o["name"];
- if (!SyncProfiles.ContainsKey(id))
- SyncProfiles.Add(id, name);
- DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
- break;
-
- case "list_config":
- break;
-
- case "column":
- break;
-
- case "column_sort":
- break;
-
- case "equalizer_profile":
- break;
-
- case "keyboard_shortcut_profile":
- break;
-
- case "keyboard_shortcut":
- break;
-
- case "song":
- break;
-
- case "album":
- break;
-
- case "artist":
- break;
-
- case "user":
- break;
- }
- }
-
- /// <summary>
- /// Deletes an object.
- /// </summary>
- /// <remarks>
- /// Use this when an object is changed from outside the application (ie async call from server)
- /// in order to prevent the manager to send the update back to the server.
- /// </remarks>
- /// <param name="objectType">The name of the type of object</param>
- /// <param name="objectID">The object ID</param>
- public static void DeleteObject(string objectType, int objectID)
- {
- switch (objectType)
- {
- case "device":
- if (objectID == Identity.DeviceID)
- {
-
- Identity.DeviceID = -1;
- RegisterDevice();
- }
- break;
-
- case "configuration":
- if (objectID == Identity.ConfigurationID)
- {
- Identity.ConfigurationID = -1;
- }
- else
- {
- object old = SyncProfiles;
- if (SyncProfiles.ContainsKey(objectID))
- SyncProfiles.Remove(objectID);
- DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
- }
- break;
-
- case "list_config":
- break;
-
- case "column":
- break;
-
- case "column_sort":
- break;
-
- case "equalizer_profile":
- break;
-
- case "keyboard_shortcut_profile":
- break;
-
- case "keyboard_shortcut":
- break;
-
- case "song":
- break;
-
- case "album":
- break;
-
- case "artist":
- break;
-
- case "user":
- break;
- }
- }
-
- /// <summary>
- /// Executes a command.
- /// </summary>
- /// <param name="command">The name of the command</param>
- /// <param name="configID">The ID of the config</param>
- public static void ExecuteCommand(string command, string configID)
- {
- if (Identity.ConfigurationID != Convert.ToInt32(configID))
- return;
-
- switch (command)
- {
- case "next":
- MediaManager.Next(true, true);
- break;
-
- case "previous":
- MediaManager.Previous();
- break;
- }
- }
-
- /// <summary>
- /// Adds a new cloud configuration.
- /// </summary>
- /// <param name="name">The name of the new configuration.</param>
- /// <param name="use">Switch to use the new configuration after it has been created.</param>
- public static void AddCloudConfiguration(string name, bool use = false)
- {
- ThreadStart thread = delegate()
- {
- U.L(LogLevel.Debug, "SERVICE", "Adding cloud configuration named '" + name + "'");
- string url = String.Format("/configurations.json");
-
- // set all parameters
- string mediaState = SettingsManager.MediaState.ToString();
- string repeat = SettingsManager.Repeat.ToString();
- string shuffle = SettingsManager.Shuffle ? "Random" : "Off";
- string volume = SettingsManager.Volume.ToString();
-
- // create query string
- string query = String.Format("?configuration[name]={0}" +
- "&configuration[media_state]={1}" +
- "&configuration[repeat]={2}" +
- "&configuration[shuffle]={3}" +
- "&configuration[volume]={4}",
- OAuth.Manager.UrlEncode(name),
- OAuth.Manager.UrlEncode(mediaState),
- OAuth.Manager.UrlEncode(repeat),
- OAuth.Manager.UrlEncode(shuffle),
- OAuth.Manager.UrlEncode(volume));
-
-
- // send post request to server
- var response = SendRequest(url, "POST", query);
- if (response == null || response.StatusCode != HttpStatusCode.Created)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem creating configuration");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- U.L(LogLevel.Debug, "SERVICE", "Configuration was created successfully");
- if (use)
- {
- JObject o = ParseResponse(response);
- if (o != null)
- Identity.ConfigurationID = (int)o["id"];
- }
- }
- if (response != null) response.Close();
- };
- Thread th = new Thread(thread);
- th.Name = "Service thread";
- th.Priority = ThreadPriority.Lowest;
- th.Start();
- }
-
- /// <summary>
- /// Removes a cloud configuration.
- /// </summary>
- /// <param name="id">The ID of the configuration to remove</param>
- public static void RemoveCloudConfiguration(int id)
- {
- ThreadStart thread = delegate()
- {
- U.L(LogLevel.Debug, "SERVICE", "Removing cloud configuration with ID " + id);
- string url = String.Format("/configurations/{0}.json", id);
- var response = SendRequest(url, "DELETE");
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem removing configuration");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- if (id == Identity.ConfigurationID)
- Identity.ConfigurationID = -1;
- U.L(LogLevel.Debug, "SERVICE", "Configuration was removed successfully");
- }
- };
- Thread th = new Thread(thread);
- th.Name = "Service thread";
- th.Priority = ThreadPriority.Lowest;
- th.Start();
- }
-
- /// <summary>
- /// Renames a cloud configuration.
- /// </summary>
- /// <param name="id">The ID of the configuration to rename</param>
- /// <param name="newName">The new name of the configuration</param>
- public static void RenameCloudConfiguration(int id, string newName)
- {
- ThreadStart thread = delegate()
- {
- U.L(LogLevel.Debug, "SERVICE", "Renaming cloud configuration with ID " + id + " to '" + newName + "'");
- string url = String.Format("/configurations/{0}.json", id);
- string query = String.Format("?configuration[name]={0}", OAuth.Manager.UrlEncode(newName));
- var response = SendRequest(url, "PUT", query);
- if (response == null || response.StatusCode != HttpStatusCode.Created)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem renaming configuration");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- U.L(LogLevel.Debug, "SERVICE", "Configuration was renamed successfully");
- if (response != null) response.Close();
- };
- Thread th = new Thread(thread);
- th.Name = "Service thread";
- th.Priority = ThreadPriority.Lowest;
- th.Start();
- }
-
- /// <summary>
- /// Synchronize the application with a given cloud configuration.
- /// Will download all information and apply it to the application.
- ///
- /// Set to 0 to turn off.
- /// </summary>
- /// <param name="cloudConfig">The ID of the cloud configuration</param>
- public static void Sync(int cloudConfig)
- {
- if (cloudConfig <= 0)
- {
- U.L(LogLevel.Debug, "SERVICE", "Turning off synchronization");
- Identity.ConfigurationID = 0;
- return;
- }
- ThreadStart thread = delegate()
- {
- U.L(LogLevel.Debug, "SERVICE", "Retrieving cloud configuration with ID " + cloudConfig);
- string url = String.Format("/configurations/{0}.json", cloudConfig);
- var response = SendRequest(url, "GET");
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem retrieving configuration");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- JObject config = ParseResponse(response);
- int id = (int)config["id"];
- Identity.ConfigurationID = id;
- SyncProfileUpdated(config);
-
- U.L(LogLevel.Debug, "SERVICE", "Tell server we're using this profile");
- url = String.Format("/devices/{0}.json", Identity.DeviceID);
- string query = String.Format("?device[configuration_id]={0}",
- OAuth.Manager.UrlEncode(Identity.ConfigurationID.ToString()));
- response.Close();
- response = SendRequest(url, "PUT", query);
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem updating device data");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- U.L(LogLevel.Debug, "SERVICE", "Successfully told server of our new sync profile.");
- }
- if (response != null) response.Close();
- }
- if (response != null) response.Close();
- };
- Thread th = new Thread(thread);
- th.Name = "Service thread";
- th.Priority = ThreadPriority.Lowest;
- th.Start();
- }
-
- /// <summary>
- /// Initializes the oauth manager and acquires a request token if needed
- /// </summary>
- public static void InitOAuth()
- {
- oauth = new OAuth.Manager();
- oauth["consumer_key"] = oauth_key;
- oauth["consumer_secret"] = oauth_secret;
- if (Linked)
- {
- oauth["token"] = SettingsManager.OAuthToken;
- oauth["token_secret"] = SettingsManager.OAuthSecret;
- RetrieveUserData();
- }
- else
- {
- try
- {
- oauth.AcquireRequestToken(domain + "/oauth/request_token", "POST");
- oauth_request_token = oauth["token"];
- }
- catch (Exception e)
- {
- U.L(LogLevel.Warning, "SERVICE", "Problem linking with account: " + e.Message);
- DispatchDisconnected();
- }
- }
- }
-
- /// <summary>
- /// Checks if a given URL works.
- /// If not it will go to disconnected mode.
- /// </summary>
- /// <param name="url">The URL to check</param>
- public static void Ping(Uri url)
- {
- ThreadStart thread = delegate()
- {
- try
- {
- U.L(LogLevel.Debug, "SERVICE", "Trying to ping: " + url.Host);
- var request = (HttpWebRequest)WebRequest.Create(url.Scheme + "://" + url.Host);
- request.Timeout = 10000;
- request.KeepAlive = false;
- HttpWebResponse resp = (HttpWebResponse)request.GetResponse();
- if (resp != null)
- {
- // check status codes?
- }
- else
- {
- U.L(LogLevel.Error, "SERVICE", "No response from host: " + url.Host);
- DispatchDisconnected();
- }
- resp.Close();
- }
- catch (WebException e)
- {
- U.L(LogLevel.Error, "SERVICE", "Error while pinging URL: " + e.Message);
- DispatchDisconnected();
- }
- };
- Thread th = new Thread(thread);
- th.Name = "Ping thread";
- th.Priority = ThreadPriority.Lowest;
- th.Start();
- }
-
- #endregion
-
- #region Private
-
- /// <summary>
- /// Retrieves the data of the currently linked user.
- /// </summary>
- public static void RetrieveUserData()
- {
- if (!Linked) return;
-
- ThreadStart cloudThread = delegate()
- {
- string name = U.Capitalize(Environment.MachineName);
- U.L(LogLevel.Debug, "SERVICE", "Fetching user data");
- string url = String.Format("/me.json");
- var response = SendRequest(url, "GET", "");
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem retrieving user data");
- U.L(LogLevel.Error, "SERVICE", response);
- Delink();
- }
- else
- {
- JObject o = ParseResponse(response);
- userID = (int)o["id"];
- Console.WriteLine("user id: " + userID);
- if (!SettingsManager.HasCloudIdentity(userID))
- SettingsManager.CloudIdentities.Add(new CloudIdentity { UserID = userID });
- RegisterDevice();
- }
- if (response != null) response.Close();
- };
- Thread cl_thread = new Thread(cloudThread);
- cl_thread.Name = "Cloud user thread";
- cl_thread.Priority = ThreadPriority.Lowest;
- cl_thread.Start();
- }
-
- /// <summary>
- /// Registers the device with the server
- /// </summary>
- private static void RegisterDevice()
- {
- ThreadStart cloudThread = delegate()
- {
- if (Identity != null && Identity.DeviceID > 0)
- {
- U.L(LogLevel.Debug, "SERVICE", "Device already registered, verifying ID");
- string url = String.Format("/devices/{0}.json", Identity.DeviceID);
- var response = SendRequest(url, "GET");
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- Identity.DeviceID = -1;
- U.L(LogLevel.Debug, "SERVICE", "Previous registered ID was invalid");
- RegisterDevice();
- }
- else
- {
- Stream s = response.GetResponseStream();
- StreamReader sr = new StreamReader(s);
- string str = sr.ReadToEnd();
- JObject o = JObject.Parse(str);
- string name = (string)o["name"];
- U.L(LogLevel.Debug, "SERVICE", "ID verified, device name is " + name);
- deviceName = name;
- DeviceName = name;
- DispatchPropertyChanged("Linked", false, true);
- RetrieveCloudConfigurations();
- }
- if (response != null) response.Close();
- }
- else
- {
- string name = U.Capitalize(Environment.MachineName);
- U.L(LogLevel.Debug, "SERVICE", "Need to register the device");
- string url = String.Format("/devices.json");
- string query = String.Format("?device[name]={0}&device[version]={1}",
- OAuth.Manager.UrlEncode(name),
- OAuth.Manager.UrlEncode(SettingsManager.Release));
- var response = SendRequest(url, "POST", query);
- if (response == null || response.StatusCode != HttpStatusCode.Created)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem registering the device");
- U.L(LogLevel.Error, "SERVICE", response);
- Delink();
- }
- else
- {
- Stream s = response.GetResponseStream();
- StreamReader sr = new StreamReader(s);
- string str = sr.ReadToEnd();
- JObject o = JObject.Parse(str);
- Identity.DeviceID = (int)o["id"];
- U.L(LogLevel.Debug, "SERVICE", "Device has been registered with ID " + Identity.DeviceID.ToString());
- deviceName = name;
- DeviceName = name;
- DispatchPropertyChanged("Linked", false, true);
- RetrieveCloudConfigurations();
- }
- if (response != null) response.Close();
- }
- };
- Thread cl_thread = new Thread(cloudThread);
- cl_thread.Name = "Cloud register thread";
- cl_thread.Priority = ThreadPriority.Lowest;
- cl_thread.Start();
- }
-
- /// <summary>
- /// Retrieves all cloud configurations from the server.
- /// </summary>
- private static void RetrieveCloudConfigurations()
- {
- ThreadStart configThread = delegate()
- {
- U.L(LogLevel.Debug, "SERVICE", "Retrieving cloud configurations");
- string url = String.Format("/configurations.json");
- var response = SendRequest(url, "GET");
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem retrieving cloud configurations");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- Stream s = response.GetResponseStream();
- StreamReader sr = new StreamReader(s);
- string str = sr.ReadToEnd();
- Console.WriteLine("response: " + str);
- JArray a = JArray.Parse(str);
- object old = SyncProfiles;
- SyncProfiles.Clear();
- foreach (JObject config in a)
- {
- string name = (string)config["name"];
- int id = (int)config["id"];
- if (SyncProfiles.ContainsKey(id))
- SyncProfiles[id] = name;
- else
- SyncProfiles.Add(id, name);
- }
- DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
- }
- if (response != null) response.Close();
- };
- Thread config_thread = new Thread(configThread);
- config_thread.Name = "Cloud configuration thread";
- config_thread.Priority = ThreadPriority.Lowest;
- config_thread.Start();
- }
-
- /// <summary>
- /// Will update Stoffi's current configuration according to the
- /// state of the synchronization profile.
- /// </summary>
- /// <param name="updatedParams">The updated parameters of the configuration.</param>
- private static void SyncProfileUpdated(JObject updatedParams)
- {
- SettingsManager.PropertyChanged -= SettingsManager_PropertyChanged;
-
- #region Media state
- if (updatedParams["media_state"] != null)
- {
- try
- {
- switch ((string)updatedParams["media_state"])
- {
- case "Playing":
- MediaManager.Play();
- break;
-
- case "Paused":
- Console.WriteLine("PAUSE!");
- MediaManager.Pause();
- break;
-
- case "Stopped":
- Console.WriteLine("STOP!");
- MediaManager.Stop();
- break;
- }
- }
- catch (Exception e)
- {
- U.L(LogLevel.Error, "SERVICE", "Problem synchronizing media state: " + e.Message);
- }
- }
- #endregion
-
- #region Shuffle
- if (updatedParams["shuffle"] != null)
- {
- try
- {
- switch ((string)updatedParams["shuffle"])
- {
- case "NoShuffle":
- SettingsManager.Shuffle = false;
- break;
-
- case "Random":
- SettingsManager.Shuffle = true;
- break;
- }
- }
- catch (Exception e)
- {
- U.L(LogLevel.Error, "SERVICE", "Problem synchronizing shuffle mode: " + e.Message);
- }
- }
- #endregion
-
- #region Repeat
- if (updatedParams["repeat"] != null)
- {
- try
- {
- switch ((string)updatedParams["repeat"])
- {
- case "NoRepeat":
- SettingsManager.Repeat = RepeatState.NoRepeat;
- break;
-
- case "RepeatAll":
- SettingsManager.Repeat = RepeatState.RepeatAll;
- break;
-
- case "RepeatOne":
- SettingsManager.Repeat = RepeatState.RepeatOne;
- break;
- }
- }
- catch (Exception e)
- {
- U.L(LogLevel.Error, "SERVICE", "Problem synchronizing repeat mode: " + e.Message);
- }
- }
- #endregion
-
- #region Volume
- if (updatedParams["volume"] != null)
- {
- try
- {
- float vol = (float)updatedParams["volume"];
- SettingsManager.Volume = Convert.ToDouble(vol);
- }
- catch (Exception e)
- {
- try
- {
- string vol = (string)updatedParams["volume"];
- SettingsManager.Volume = Convert.ToDouble(vol);
- }
- catch (Exception ee)
- {
- U.L(LogLevel.Error, "SERVICE", "Problem synchronizing volume");
- U.L(LogLevel.Error, "SERVICE", e.Message);
- U.L(LogLevel.Error, "SERVICE", ee.Message);
- }
- }
- }
- #endregion
-
- #region Current track
- if (updatedParams["current_track"] != null)
- {
- try
- {
- string path = (string)updatedParams["current_track"]["path"];
- if (path != null)
- {
- // TODO:
- // Create function SettingsManager.FindTrack()
- // It should then either find a local track fast
- // or convert the path into a youtube track.
- if (YouTubeManager.IsYouTube(path))
- {
- TrackData track = YouTubeManager.CreateTrack(YouTubeManager.GetYouTubeID(path));
- if (track != null)
- SettingsManager.CurrentTrack = track;
- }
- else
- {
- foreach (TrackData track in SettingsManager.FileTracks)
- {
- if (track.Path == path)
- {
- SettingsManager.CurrentTrack = track;
- break;
- }
- }
- }
- }
- }
- catch (Exception e)
- {
- U.L(LogLevel.Error, "SERVICE", "Could not read current track from server");
- U.L(LogLevel.Error, "SERVICE", e.Message);
- }
- }
- #endregion
-
- SettingsManager.PropertyChanged += SettingsManager_PropertyChanged;
- }
-
- /// <summary>
- /// Sends a request to the server
- /// </summary>
- /// <param name="url">The base url</param>
- /// <param name="method">The HTTP method to use</param>
- /// <param name="query">The query string, encoded properly</param>
- /// <returns>The response from the server</returns>
- private static HttpWebResponse SendRequest(string url, string method, string query = "")
- {
- if (SettingsManager.HasCloudIdentity(userID))
- {
- if (query == "") query = "?";
- else query += "&";
- query += "device_id=" + Identity.DeviceID.ToString();
- }
-
- string fullUrl = domain + url + query;
- U.L(LogLevel.Debug, "SERVICE", "Sending " + method + " request to " + fullUrl);
- var authzHeader = oauth.GenerateAuthzHeader(fullUrl, method);
- var request = (HttpWebRequest)WebRequest.Create(fullUrl);
- request.Method = method;
- request.PreAuthenticate = true;
- request.AllowWriteStreamBuffering = true;
- request.Headers.Add("Authorization", authzHeader);
- request.Timeout = 30000;
- request.KeepAlive = false;
- try
- {
- HttpWebResponse resp = (HttpWebResponse)request.GetResponse();
- return resp;
- }
- catch (WebException exc)
- {
- U.L(LogLevel.Error, "SERVICE", "Error while contacting cloud server: " + exc.Message);
- return null;
- }
- }
-
- /// <summary>
- /// Parses a web response encoded in JSON into an object.
- /// </summary>
- /// <param name="response">The JSON web response.</param>
- /// <returns>A JSON object.</returns>
- private static JObject ParseResponse(HttpWebResponse response)
- {
- try
- {
- Stream s = response.GetResponseStream();
- StreamReader sr = new StreamReader(s);
- string str = sr.ReadToEnd();
- JObject o = JObject.Parse(str);
- return o;
- }
- catch (Exception e)
- {
- U.L(LogLevel.Error, "SERVICE", "Could not parse response: " + e.Message);
- return null;
- }
- }
-
- /// <summary>
- /// Encodes a track object into HTTP parameters.
- /// </summary>
- /// <param name="track">The track to encode</param>
- /// <param name="prefix">An optional prefix to put on each property</param>
- /// <returns>The track encoded as "title=X&artist=Y&genre=Z..."</returns>
- private static List<string> EncodeTrack(TrackData track, string prefix = "")
- {
- List<string> paras = new List<string>();
- if (track != null)
- {
- paras.Add(U.CreateParam("title", track.Title, prefix));
- paras.Add(U.CreateParam("artist", track.Artist, prefix));
- paras.Add(U.CreateParam("album", track.Album, prefix));
- paras.Add(U.CreateParam("length", track.RawLength, prefix));
- paras.Add(U.CreateParam("genre", track.Genre, prefix));
- paras.Add(U.CreateParam("track", track.Track, prefix));
- paras.Add(U.CreateParam("path", track.Path, prefix));
- paras.Add(U.CreateParam("views", track.RawViews, prefix));
- paras.Add(U.CreateParam("bitrate", track.Bitrate, prefix));
- paras.Add(U.CreateParam("codecs", track.Codecs, prefix));
- paras.Add(U.CreateParam("channels", track.Channels, prefix));
- paras.Add(U.CreateParam("sample_rate", track.SampleRate, prefix));
- paras.Add(U.CreateParam("year", track.Year, prefix));
- }
- return paras;
- }
-
- #endregion
-
- #region Event handlers
-
- /// <summary>
- /// Invoked when a property of the settings manager is changed
- /// </summary>
- /// <param name="sender">The sender of the event</param>
- /// <param name="e">The event data</param>
- private static void SettingsManager_PropertyChanged(object sender, PropertyChangedWithValuesEventArgs e)
- {
- if (Identity == null || Identity.ConfigurationID < 1) return;
-
- // create query string
- switch (e.PropertyName)
- {
- case "Volume":
- string vol = Convert.ToString(SettingsManager.Volume);
- toBeSynced["configuration[volume]"] = OAuth.Manager.UrlEncode(vol);
- break;
-
- case "Repeat":
- toBeSynced["configuration[repeat]"] = OAuth.Manager.UrlEncode(SettingsManager.Repeat.ToString());
- break;
-
- case "Shuffle":
- toBeSynced["configuration[shuffle]"] = OAuth.Manager.UrlEncode(SettingsManager.Shuffle ? "Random" : "Off");
- break;
-
- case "MediaState":
- toBeSynced["configuration[media_state]"] = OAuth.Manager.UrlEncode(SettingsManager.MediaState.ToString());
- break;
-
- case "CurrentTrack":
- List<string> paras = EncodeTrack(SettingsManager.CurrentTrack, "configurations[current_track]");
- foreach (string para in paras)
- {
- if (para != null)
- {
- string[] p = para.Split(new char[] { '=' }, 2);
- if (p[0] != "" && p[1] != "")
- toBeSynced[p[0]] = p[1];
- }
- }
- break;
-
- default:
- return;
- }
-
- if (syncDelay != null)
- syncDelay.Dispose();
- syncDelay = new Timer(PerformSync, null, 400, 3600);
- }
-
- /// <summary>
- /// Sends requests to the server updating the properties stored in
- /// toBeSynced
- /// </summary>
- /// <param name="state">The state (not used)</param>
- private static void PerformSync(object state)
- {
- syncDelay.Dispose();
- syncDelay = null;
-
- int id = Identity.ConfigurationID;
- if (id < 0 || toBeSynced.Count == 0) return;
-
- ThreadStart thread = delegate()
- {
- U.L(LogLevel.Debug, "SERVICE", "Sending parameters to sync server");
- string url = String.Format("/configurations/{0}.json", id);
- string query = "";
- foreach (KeyValuePair<string, string> p in toBeSynced)
- query += "&" + p.Key + "=" + p.Value;
- query = "?" + query.Substring(1);
-
- U.L(LogLevel.Debug, "SERVICE", "Updating cloud configuration with ID " + id);
-
- // send put request to server
- var response = SendRequest(url, "PUT", query);
- if (response == null || response.StatusCode != HttpStatusCode.OK)
- {
- U.L(LogLevel.Error, "SERVICE", "There was a problem updating configuration");
- U.L(LogLevel.Error, "SERVICE", response);
- }
- else
- {
- U.L(LogLevel.Debug, "SERVICE", "Configuration was updated successfully");
- }
- if (response != null) response.Close();
- };
- Thread th = new Thread(thread);
- th.Name = "Sync thread";
- th.Priority = ThreadPriority.Lowest;
- th.Start();
- }
-
- #endregion
-
- #region Dispatchers
-
- /// <summary>
- /// Trigger the PropertyChanged event
- /// </summary>
- /// <param name="name">The name of the property that was changed</param>
- /// <param name="oldValue">The value of the property before the change</param>
- /// <param name="newValue">The value of the property after the change</param>
- /// <param name="force">Dispatch even when no change occured between values</param>
- public static void DispatchPropertyChanged(string name, object oldValue, object newValue, bool force = false)
- {
- if (PropertyChanged != null && (oldValue != newValue || force))
- PropertyChanged(null, new PropertyChangedWithValuesEventArgs(name, oldValue, newValue));
- }
-
- /// <summary>
- /// Trigger the Initialized event.
- /// </summary>
- public static void DispatchInitialized()
- {
- if (Initialized != null)
- Initialized(null, new EventArgs());
- }
-
- /// <summary>
- /// Trigger the Disconnected event.
- /// </summary>
- public static void DispatchDisconnected()
- {
- if (Disconnected != null)
- Disconnected(null, new EventArgs());
- }
-
- #endregion
-
- #endregion
-
- #region Events
-
- /// <summary>
- /// Occurs when a property has been changed
- /// </summary>
- public static event PropertyChangedWithValuesEventHandler PropertyChanged;
-
- /// <summary>
- /// Occurs when the service manager has been fully initialized.
- /// </summary>
- public static event EventHandler Initialized;
-
- /// <summary>
- /// Occurs when the service manager has been disconnected from service.
- /// </summary>
- public static event EventHandler Disconnected;
-
- #endregion
- }
-
- /// <summary>
- /// Describes the interface that the async javascript will call to update attributes
- /// </summary>
- [ComVisibleAttribute(true)]
- public class CloudSyncInterface
- {
- /// <summary>
- /// Invoked when an object changes.
- /// </summary>
- /// <param name="objectType">The name of the type of object</param>
- /// <param name="objectID">The object ID</param>
- /// <param name="properties">The updated properties encoded in JSON</param>
- public void UpdateObject(string objectType, int objectID, string properties)
- {
- ServiceManager.UpdateObject(objectType, objectID, properties);
- }
-
- /// <summary>
- /// Invoked when an object is created.
- /// </summary>
- /// <param name="objectType">The name of the type of object</param>
- /// <param name="objectJSON">The object encoded as JSON</param>
- public void CreateObject(string objectType, string objectJSON)
- {
- ServiceManager.CreateObject(objectType, objectJSON);
- }
-
- /// <summary>
- /// Invoked when an object is deleted.
- /// </summary>
- /// <param name="objectType">The name of the type of object</param>
- /// <param name="objectID">The object id</param>
- public void DeleteObject(string objectType, int objectID)
- {
- ServiceManager.DeleteObject(objectType, objectID);
- }
-
- /// <summary>
- /// Invoked when a command is to be executed.
- /// </summary>
- /// <param name="command">The name of the command</param>
- /// <param name="configID">The ID of the config</param>
- public void Execute(string command, string configID)
- {
- ServiceManager.ExecuteCommand(command, configID);
- }
- }
-
- #region Delegates
- #endregion
-
- #region Event arguments
- #endregion
-
- #region Enums
- #endregion
- }