/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

  1. /**
  2. * ServiceManager.cs
  3. *
  4. * Communicates with the Stoffi Services.
  5. *
  6. * * * * * * * * *
  7. *
  8. * Copyright 2012 Simplare
  9. *
  10. * This code is part of the Stoffi Music Player Project.
  11. * Visit our website at: stoffiplayer.com
  12. *
  13. * This program is free software; you can redistribute it and/or
  14. * modify it under the terms of the GNU General Public License
  15. * as published by the Free Software Foundation; either version
  16. * 3 of the License, or (at your option) any later version.
  17. *
  18. * See stoffiplayer.com/license for more information.
  19. **/
  20. using System;
  21. using System.Collections;
  22. using System.Collections.Generic;
  23. using System.Collections.ObjectModel;
  24. using System.Collections.Specialized;
  25. using System.IO;
  26. using System.Linq;
  27. using System.Globalization;
  28. using System.Runtime.InteropServices;
  29. using System.Net;
  30. using System.Text;
  31. using System.Threading;
  32. using System.Xml;
  33. using System.Web;
  34. using Newtonsoft.Json.Linq;
  35. namespace Stoffi
  36. {
  37. /// <summary>
  38. /// Represents a manager that handles communication with
  39. /// the Stoffi services.
  40. /// </summary>
  41. public static class ServiceManager
  42. {
  43. #region Members
  44. private static OAuth.Manager oauth = new OAuth.Manager();
  45. private static string oauth_request_token = "";
  46. private static string oauth_key = "baAito0V8WXtdpfjrE4GfUhld4IvFMd9Ud5EYw8i";
  47. private static string oauth_secret = "cU1esKuX0VruYYVhU5Mrry4SukK5yL9uHcYoHip1";
  48. private static string domain = "http://alpha.stoffiplayer.com";
  49. private static string deviceName = "";
  50. private static Dictionary<string, string> toBeSynced = new Dictionary<string,string>();
  51. private static Timer syncDelay = null;
  52. private static int userID = -1;
  53. private static string lang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
  54. #endregion
  55. #region Properties
  56. /// <summary>
  57. /// Gets whether the client is linked to a service account or not
  58. /// </summary>
  59. public static bool Linked
  60. {
  61. get
  62. {
  63. return SettingsManager.OAuthToken != null &&
  64. SettingsManager.OAuthToken != "" &&
  65. SettingsManager.OAuthSecret != null &&
  66. SettingsManager.OAuthSecret != "";
  67. }
  68. }
  69. /// <summary>
  70. /// Gets the URL for authorizing the application with OAuth
  71. /// </summary>
  72. public static string RequestURL
  73. {
  74. get
  75. {
  76. return String.Format("{0}/{1}/oauth/authorize?oauth_token={2}",
  77. domain,
  78. lang,
  79. oauth_request_token);
  80. }
  81. }
  82. /// <summary>
  83. /// Gets the URL for logging out
  84. /// </summary>
  85. public static string LogoutURL { get { return String.Format("{0}/{1}/logout", domain, lang); } }
  86. /// <summary>
  87. /// Gets the Identity representing the currently linked user.
  88. /// </summary>
  89. public static CloudIdentity Identity
  90. {
  91. get
  92. {
  93. return SettingsManager.GetCloudIdentity(userID);
  94. }
  95. }
  96. /// <summary>
  97. /// Gets the callback URL for OAuth
  98. /// </summary>
  99. public static string CallbackURL
  100. {
  101. get { return String.Format("{0}/controls", domain); }
  102. }
  103. /// <summary>
  104. /// Gets the callback URL with parameters to authenticate
  105. /// </summary>
  106. public static string CallbackURLWithAuthParams
  107. {
  108. get
  109. {
  110. return String.Format("{0}/{1}/controls?callback=stoffi&oauth_token={2}&oauth_secret_token={3}",
  111. domain,
  112. SettingsManager.Culture.TwoLetterISOLanguageName,
  113. SettingsManager.OAuthToken,
  114. SettingsManager.OAuthSecret);
  115. }
  116. }
  117. /// <summary>
  118. /// Gets or sets the name of the device
  119. /// </summary>
  120. public static string DeviceName
  121. {
  122. get { return deviceName; }
  123. set
  124. {
  125. object old = deviceName;
  126. deviceName = value;
  127. DispatchPropertyChanged("DeviceName", old, value, true);
  128. if (old != value as object)
  129. {
  130. U.L(LogLevel.Debug, "SERVICE", "Changing device name");
  131. string url = String.Format("/devices/{0}.json", Identity.DeviceID);
  132. string query = String.Format("?device[name]={0}&device[version]={1}",
  133. OAuth.Manager.UrlEncode(value),
  134. OAuth.Manager.UrlEncode(SettingsManager.Release));
  135. var response = SendRequest(url, "PUT", query);
  136. if (response == null || response.StatusCode != HttpStatusCode.OK)
  137. {
  138. U.L(LogLevel.Error, "SERVICE", "There was a problem changing device name");
  139. U.L(LogLevel.Error, "SERVICE", response);
  140. }
  141. else
  142. {
  143. U.L(LogLevel.Debug, "SERVICE", "Device name changed successfully");
  144. }
  145. if (response != null) response.Close();
  146. }
  147. }
  148. }
  149. /// <summary>
  150. /// Gets the synchronization profiles.
  151. /// </summary>
  152. public static Dictionary<int, string> SyncProfiles { get; private set; }
  153. #endregion
  154. /// <summary>
  155. /// Creates an instance of the ServiceManager class.
  156. /// </summary>
  157. static ServiceManager()
  158. {
  159. SyncProfiles = new Dictionary<int, string>();
  160. }
  161. #region Methods
  162. #region Public
  163. /// <summary>
  164. /// Initializes the manager.
  165. /// </summary>
  166. public static void Initialize()
  167. {
  168. ServicePointManager.DefaultConnectionLimit = 1000;
  169. ThreadStart initThread = delegate()
  170. {
  171. InitOAuth();
  172. SyncProfiles = new Dictionary<int, string>();
  173. SettingsManager.PropertyChanged += SettingsManager_PropertyChanged;
  174. DispatchInitialized();
  175. };
  176. Thread init_thread = new Thread(initThread);
  177. init_thread.Name = "Service Initializer thread";
  178. init_thread.Priority = ThreadPriority.Lowest;
  179. init_thread.Start();
  180. }
  181. /// <summary>
  182. /// Shares a track.
  183. /// </summary>
  184. /// <param name="track">The track to share</param>
  185. public static void ShareSong(TrackData track)
  186. {
  187. ThreadStart shareThread = delegate()
  188. {
  189. string query = String.Format("?title={0}&artist={1}&path={2}&object={3}",
  190. OAuth.Manager.UrlEncode(track.Title),
  191. OAuth.Manager.UrlEncode(track.Artist),
  192. OAuth.Manager.UrlEncode(track.Path),
  193. "song");
  194. var response = SendRequest("/shares.json", "POST", query);
  195. if (response == null || response.StatusCode != HttpStatusCode.OK)
  196. {
  197. U.L(LogLevel.Error, "SERVICE" ,"There was a problem sharing song");
  198. U.L(LogLevel.Error, "SERVICE", response);
  199. }
  200. else
  201. {
  202. U.L(LogLevel.Information, "SERVICE", "Shared song successfully");
  203. }
  204. if (response != null) response.Close();
  205. };
  206. Thread sharethread = new Thread(shareThread);
  207. sharethread.Name = "Share thread";
  208. sharethread.Priority = ThreadPriority.Lowest;
  209. sharethread.Start();
  210. }
  211. /// <summary>
  212. /// Shares a playlist.
  213. /// </summary>
  214. /// <param name="pl">The playlist to share</param>
  215. public static void SharePlaylist(PlaylistData pl)
  216. {
  217. ThreadStart shareThread = delegate()
  218. {
  219. //string query = String.Format("?title={0}&artist={1}&path={2}&type={3}",
  220. // OAuth.Manager.UrlEncode(track.Title),
  221. // OAuth.Manager.UrlEncode(track.Artist),
  222. // OAuth.Manager.UrlEncode(track.Path),
  223. // "song");
  224. //var response = SendRequest("/share.json", "POST", query);
  225. //if (response == null || response.StatusCode != HttpStatusCode.OK)
  226. //{
  227. // U.L(LogLevel.Error, "SERVICE", "There was a problem sharing song");
  228. // U.L(LogLevel.Error, "SERVICE", response);
  229. //}
  230. //else
  231. //{
  232. // U.L(LogLevel.Information, "SERVICE", "Shared playlist successfully");
  233. //}
  234. //if (response != null) response.Close();
  235. };
  236. Thread sharethread = new Thread(shareThread);
  237. sharethread.Name = "Share thread";
  238. sharethread.Priority = ThreadPriority.Lowest;
  239. sharethread.Start();
  240. }
  241. /// <summary>
  242. /// Submits that a track was listened to.
  243. /// </summary>
  244. /// <param name="track">The track that was listened to</param>
  245. /// <param name="playlist">The playlist from which the song was played</param>
  246. public static void Listen(TrackData track, PlaylistData playlist = null)
  247. {
  248. if (!SettingsManager.SubmitSongs) return;
  249. ThreadStart listenThread = delegate()
  250. {
  251. string query = String.Format("?title={0}&artist={1}&path={2}&playlist={3}",
  252. OAuth.Manager.UrlEncode(track.Title),
  253. OAuth.Manager.UrlEncode(track.Artist),
  254. OAuth.Manager.UrlEncode(track.Path),
  255. playlist == null ? "" : playlist.Name);
  256. var response = SendRequest("/listens.json", "POST", query);
  257. if (response == null || response.StatusCode != HttpStatusCode.Created)
  258. {
  259. U.L(LogLevel.Error, "SERVICE", "There was a problem submitting song");
  260. U.L(LogLevel.Error, "SERVICE", response);
  261. }
  262. else
  263. {
  264. U.L(LogLevel.Information, "SERVICE", "Submitted song successfully");
  265. }
  266. if (response != null) response.Close();
  267. };
  268. Thread l_thread = new Thread(listenThread);
  269. l_thread.Name = "Submission thread";
  270. l_thread.Priority = ThreadPriority.Lowest;
  271. l_thread.Start();
  272. }
  273. /// <summary>
  274. /// Link Stoffi to an account
  275. /// </summary>
  276. /// <param name="url">The full callback URL (including parameters) where OAuth sent us after authorization</param>
  277. public static void Link(string url)
  278. {
  279. U.L(LogLevel.Information, "SERVICE", "Linking to service account");
  280. Dictionary<string,string> p = U.GetParams(U.GetQuery(url));
  281. string token = p["oauth_token"];
  282. string verifier = p["oauth_verifier"];
  283. var access_url = domain + "/oauth/access_token";
  284. try
  285. {
  286. OAuth.OAuthResponse accessToken = oauth.AcquireAccessToken(access_url, "POST", verifier);
  287. string[] tokens = accessToken.AllText.Split('&');
  288. SettingsManager.OAuthToken = tokens[0].Split('=')[1];
  289. SettingsManager.OAuthSecret = tokens[1].Split('=')[1];
  290. U.L(LogLevel.Information, "SERVICE", "Service account has been linked");
  291. RetrieveUserData();
  292. }
  293. catch (Exception e)
  294. {
  295. U.L(LogLevel.Warning, "SERVICE", "Problem linking with account: " + e.Message);
  296. DispatchDisconnected();
  297. }
  298. }
  299. /// <summary>
  300. /// Remove the current link if there is one
  301. /// </summary>
  302. public static void Delink()
  303. {
  304. if (!Linked) return;
  305. SettingsManager.OAuthToken = null;
  306. SettingsManager.OAuthSecret = null;
  307. try
  308. {
  309. InitOAuth();
  310. DispatchPropertyChanged("Linked", true, false);
  311. }
  312. catch (Exception e)
  313. {
  314. DispatchDisconnected();
  315. }
  316. }
  317. /// <summary>
  318. /// Update the values of an object.
  319. /// </summary>
  320. /// <remarks>
  321. /// Use this when an object is changed from outside the application (ie async call from server)
  322. /// in order to prevent the manager to send the update back to the server.
  323. /// </remarks>
  324. /// <param name="objectType">The name of the type of object</param>
  325. /// <param name="objectID">The object ID</param>
  326. /// <param name="updatedProperties">The updated properties of the object encoded in JSON</param>
  327. public static void UpdateObject(string objectType, int objectID, string updatedProperties)
  328. {
  329. JObject o = JObject.Parse(updatedProperties);
  330. switch (objectType)
  331. {
  332. case "device":
  333. if (objectID == Identity.ConfigurationID)
  334. {
  335. if (o["name"] != null)
  336. {
  337. string name = U.UnescapeHTML((string)o["name"]);
  338. deviceName = name;
  339. DeviceName = name;
  340. }
  341. }
  342. break;
  343. case "configuration":
  344. if (objectID == Identity.ConfigurationID)
  345. SyncProfileUpdated(o);
  346. else
  347. {
  348. object old = SyncProfiles;
  349. string name = (string)o["name"];
  350. if (SyncProfiles.ContainsKey(objectID))
  351. SyncProfiles[objectID] = name;
  352. else
  353. SyncProfiles.Add(objectID, name);
  354. DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
  355. }
  356. break;
  357. case "list_config":
  358. break;
  359. case "column":
  360. break;
  361. case "column_sort":
  362. break;
  363. case "equalizer_profile":
  364. break;
  365. case "keyboard_shortcut_profile":
  366. break;
  367. case "keyboard_shortcut":
  368. break;
  369. case "song":
  370. break;
  371. case "album":
  372. break;
  373. case "artist":
  374. break;
  375. case "user":
  376. break;
  377. }
  378. }
  379. /// <summary>
  380. /// Creates a new object.
  381. /// </summary>
  382. /// <remarks>
  383. /// Use this when an object is changed from outside the application (ie async call from server)
  384. /// in order to prevent the manager to send the update back to the server.
  385. /// </remarks>
  386. /// <param name="objectType">The type of the object</param>
  387. /// <param name="objectJSON">The object encoded in JSON</param>
  388. public static void CreateObject(string objectType, string objectJSON)
  389. {
  390. JObject o = JObject.Parse(objectJSON);
  391. switch (objectType)
  392. {
  393. case "device":
  394. break;
  395. case "configuration":
  396. object old = SyncProfiles;
  397. int id = Convert.ToInt32((string)o["id"]);
  398. string name = (string)o["name"];
  399. if (!SyncProfiles.ContainsKey(id))
  400. SyncProfiles.Add(id, name);
  401. DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
  402. break;
  403. case "list_config":
  404. break;
  405. case "column":
  406. break;
  407. case "column_sort":
  408. break;
  409. case "equalizer_profile":
  410. break;
  411. case "keyboard_shortcut_profile":
  412. break;
  413. case "keyboard_shortcut":
  414. break;
  415. case "song":
  416. break;
  417. case "album":
  418. break;
  419. case "artist":
  420. break;
  421. case "user":
  422. break;
  423. }
  424. }
  425. /// <summary>
  426. /// Deletes an object.
  427. /// </summary>
  428. /// <remarks>
  429. /// Use this when an object is changed from outside the application (ie async call from server)
  430. /// in order to prevent the manager to send the update back to the server.
  431. /// </remarks>
  432. /// <param name="objectType">The name of the type of object</param>
  433. /// <param name="objectID">The object ID</param>
  434. public static void DeleteObject(string objectType, int objectID)
  435. {
  436. switch (objectType)
  437. {
  438. case "device":
  439. if (objectID == Identity.DeviceID)
  440. {
  441. Identity.DeviceID = -1;
  442. RegisterDevice();
  443. }
  444. break;
  445. case "configuration":
  446. if (objectID == Identity.ConfigurationID)
  447. {
  448. Identity.ConfigurationID = -1;
  449. }
  450. else
  451. {
  452. object old = SyncProfiles;
  453. if (SyncProfiles.ContainsKey(objectID))
  454. SyncProfiles.Remove(objectID);
  455. DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
  456. }
  457. break;
  458. case "list_config":
  459. break;
  460. case "column":
  461. break;
  462. case "column_sort":
  463. break;
  464. case "equalizer_profile":
  465. break;
  466. case "keyboard_shortcut_profile":
  467. break;
  468. case "keyboard_shortcut":
  469. break;
  470. case "song":
  471. break;
  472. case "album":
  473. break;
  474. case "artist":
  475. break;
  476. case "user":
  477. break;
  478. }
  479. }
  480. /// <summary>
  481. /// Executes a command.
  482. /// </summary>
  483. /// <param name="command">The name of the command</param>
  484. /// <param name="configID">The ID of the config</param>
  485. public static void ExecuteCommand(string command, string configID)
  486. {
  487. if (Identity.ConfigurationID != Convert.ToInt32(configID))
  488. return;
  489. switch (command)
  490. {
  491. case "next":
  492. MediaManager.Next(true, true);
  493. break;
  494. case "previous":
  495. MediaManager.Previous();
  496. break;
  497. }
  498. }
  499. /// <summary>
  500. /// Adds a new cloud configuration.
  501. /// </summary>
  502. /// <param name="name">The name of the new configuration.</param>
  503. /// <param name="use">Switch to use the new configuration after it has been created.</param>
  504. public static void AddCloudConfiguration(string name, bool use = false)
  505. {
  506. ThreadStart thread = delegate()
  507. {
  508. U.L(LogLevel.Debug, "SERVICE", "Adding cloud configuration named '" + name + "'");
  509. string url = String.Format("/configurations.json");
  510. // set all parameters
  511. string mediaState = SettingsManager.MediaState.ToString();
  512. string repeat = SettingsManager.Repeat.ToString();
  513. string shuffle = SettingsManager.Shuffle ? "Random" : "Off";
  514. string volume = SettingsManager.Volume.ToString();
  515. // create query string
  516. string query = String.Format("?configuration[name]={0}" +
  517. "&configuration[media_state]={1}" +
  518. "&configuration[repeat]={2}" +
  519. "&configuration[shuffle]={3}" +
  520. "&configuration[volume]={4}",
  521. OAuth.Manager.UrlEncode(name),
  522. OAuth.Manager.UrlEncode(mediaState),
  523. OAuth.Manager.UrlEncode(repeat),
  524. OAuth.Manager.UrlEncode(shuffle),
  525. OAuth.Manager.UrlEncode(volume));
  526. // send post request to server
  527. var response = SendRequest(url, "POST", query);
  528. if (response == null || response.StatusCode != HttpStatusCode.Created)
  529. {
  530. U.L(LogLevel.Error, "SERVICE", "There was a problem creating configuration");
  531. U.L(LogLevel.Error, "SERVICE", response);
  532. }
  533. else
  534. {
  535. U.L(LogLevel.Debug, "SERVICE", "Configuration was created successfully");
  536. if (use)
  537. {
  538. JObject o = ParseResponse(response);
  539. if (o != null)
  540. Identity.ConfigurationID = (int)o["id"];
  541. }
  542. }
  543. if (response != null) response.Close();
  544. };
  545. Thread th = new Thread(thread);
  546. th.Name = "Service thread";
  547. th.Priority = ThreadPriority.Lowest;
  548. th.Start();
  549. }
  550. /// <summary>
  551. /// Removes a cloud configuration.
  552. /// </summary>
  553. /// <param name="id">The ID of the configuration to remove</param>
  554. public static void RemoveCloudConfiguration(int id)
  555. {
  556. ThreadStart thread = delegate()
  557. {
  558. U.L(LogLevel.Debug, "SERVICE", "Removing cloud configuration with ID " + id);
  559. string url = String.Format("/configurations/{0}.json", id);
  560. var response = SendRequest(url, "DELETE");
  561. if (response == null || response.StatusCode != HttpStatusCode.OK)
  562. {
  563. U.L(LogLevel.Error, "SERVICE", "There was a problem removing configuration");
  564. U.L(LogLevel.Error, "SERVICE", response);
  565. }
  566. else
  567. {
  568. if (id == Identity.ConfigurationID)
  569. Identity.ConfigurationID = -1;
  570. U.L(LogLevel.Debug, "SERVICE", "Configuration was removed successfully");
  571. }
  572. };
  573. Thread th = new Thread(thread);
  574. th.Name = "Service thread";
  575. th.Priority = ThreadPriority.Lowest;
  576. th.Start();
  577. }
  578. /// <summary>
  579. /// Renames a cloud configuration.
  580. /// </summary>
  581. /// <param name="id">The ID of the configuration to rename</param>
  582. /// <param name="newName">The new name of the configuration</param>
  583. public static void RenameCloudConfiguration(int id, string newName)
  584. {
  585. ThreadStart thread = delegate()
  586. {
  587. U.L(LogLevel.Debug, "SERVICE", "Renaming cloud configuration with ID " + id + " to '" + newName + "'");
  588. string url = String.Format("/configurations/{0}.json", id);
  589. string query = String.Format("?configuration[name]={0}", OAuth.Manager.UrlEncode(newName));
  590. var response = SendRequest(url, "PUT", query);
  591. if (response == null || response.StatusCode != HttpStatusCode.Created)
  592. {
  593. U.L(LogLevel.Error, "SERVICE", "There was a problem renaming configuration");
  594. U.L(LogLevel.Error, "SERVICE", response);
  595. }
  596. else
  597. U.L(LogLevel.Debug, "SERVICE", "Configuration was renamed successfully");
  598. if (response != null) response.Close();
  599. };
  600. Thread th = new Thread(thread);
  601. th.Name = "Service thread";
  602. th.Priority = ThreadPriority.Lowest;
  603. th.Start();
  604. }
  605. /// <summary>
  606. /// Synchronize the application with a given cloud configuration.
  607. /// Will download all information and apply it to the application.
  608. ///
  609. /// Set to 0 to turn off.
  610. /// </summary>
  611. /// <param name="cloudConfig">The ID of the cloud configuration</param>
  612. public static void Sync(int cloudConfig)
  613. {
  614. if (cloudConfig <= 0)
  615. {
  616. U.L(LogLevel.Debug, "SERVICE", "Turning off synchronization");
  617. Identity.ConfigurationID = 0;
  618. return;
  619. }
  620. ThreadStart thread = delegate()
  621. {
  622. U.L(LogLevel.Debug, "SERVICE", "Retrieving cloud configuration with ID " + cloudConfig);
  623. string url = String.Format("/configurations/{0}.json", cloudConfig);
  624. var response = SendRequest(url, "GET");
  625. if (response == null || response.StatusCode != HttpStatusCode.OK)
  626. {
  627. U.L(LogLevel.Error, "SERVICE", "There was a problem retrieving configuration");
  628. U.L(LogLevel.Error, "SERVICE", response);
  629. }
  630. else
  631. {
  632. JObject config = ParseResponse(response);
  633. int id = (int)config["id"];
  634. Identity.ConfigurationID = id;
  635. SyncProfileUpdated(config);
  636. U.L(LogLevel.Debug, "SERVICE", "Tell server we're using this profile");
  637. url = String.Format("/devices/{0}.json", Identity.DeviceID);
  638. string query = String.Format("?device[configuration_id]={0}",
  639. OAuth.Manager.UrlEncode(Identity.ConfigurationID.ToString()));
  640. response.Close();
  641. response = SendRequest(url, "PUT", query);
  642. if (response == null || response.StatusCode != HttpStatusCode.OK)
  643. {
  644. U.L(LogLevel.Error, "SERVICE", "There was a problem updating device data");
  645. U.L(LogLevel.Error, "SERVICE", response);
  646. }
  647. else
  648. {
  649. U.L(LogLevel.Debug, "SERVICE", "Successfully told server of our new sync profile.");
  650. }
  651. if (response != null) response.Close();
  652. }
  653. if (response != null) response.Close();
  654. };
  655. Thread th = new Thread(thread);
  656. th.Name = "Service thread";
  657. th.Priority = ThreadPriority.Lowest;
  658. th.Start();
  659. }
  660. /// <summary>
  661. /// Initializes the oauth manager and acquires a request token if needed
  662. /// </summary>
  663. public static void InitOAuth()
  664. {
  665. oauth = new OAuth.Manager();
  666. oauth["consumer_key"] = oauth_key;
  667. oauth["consumer_secret"] = oauth_secret;
  668. if (Linked)
  669. {
  670. oauth["token"] = SettingsManager.OAuthToken;
  671. oauth["token_secret"] = SettingsManager.OAuthSecret;
  672. RetrieveUserData();
  673. }
  674. else
  675. {
  676. try
  677. {
  678. oauth.AcquireRequestToken(domain + "/oauth/request_token", "POST");
  679. oauth_request_token = oauth["token"];
  680. }
  681. catch (Exception e)
  682. {
  683. U.L(LogLevel.Warning, "SERVICE", "Problem linking with account: " + e.Message);
  684. DispatchDisconnected();
  685. }
  686. }
  687. }
  688. /// <summary>
  689. /// Checks if a given URL works.
  690. /// If not it will go to disconnected mode.
  691. /// </summary>
  692. /// <param name="url">The URL to check</param>
  693. public static void Ping(Uri url)
  694. {
  695. ThreadStart thread = delegate()
  696. {
  697. try
  698. {
  699. U.L(LogLevel.Debug, "SERVICE", "Trying to ping: " + url.Host);
  700. var request = (HttpWebRequest)WebRequest.Create(url.Scheme + "://" + url.Host);
  701. request.Timeout = 10000;
  702. request.KeepAlive = false;
  703. HttpWebResponse resp = (HttpWebResponse)request.GetResponse();
  704. if (resp != null)
  705. {
  706. // check status codes?
  707. }
  708. else
  709. {
  710. U.L(LogLevel.Error, "SERVICE", "No response from host: " + url.Host);
  711. DispatchDisconnected();
  712. }
  713. resp.Close();
  714. }
  715. catch (WebException e)
  716. {
  717. U.L(LogLevel.Error, "SERVICE", "Error while pinging URL: " + e.Message);
  718. DispatchDisconnected();
  719. }
  720. };
  721. Thread th = new Thread(thread);
  722. th.Name = "Ping thread";
  723. th.Priority = ThreadPriority.Lowest;
  724. th.Start();
  725. }
  726. #endregion
  727. #region Private
  728. /// <summary>
  729. /// Retrieves the data of the currently linked user.
  730. /// </summary>
  731. public static void RetrieveUserData()
  732. {
  733. if (!Linked) return;
  734. ThreadStart cloudThread = delegate()
  735. {
  736. string name = U.Capitalize(Environment.MachineName);
  737. U.L(LogLevel.Debug, "SERVICE", "Fetching user data");
  738. string url = String.Format("/me.json");
  739. var response = SendRequest(url, "GET", "");
  740. if (response == null || response.StatusCode != HttpStatusCode.OK)
  741. {
  742. U.L(LogLevel.Error, "SERVICE", "There was a problem retrieving user data");
  743. U.L(LogLevel.Error, "SERVICE", response);
  744. Delink();
  745. }
  746. else
  747. {
  748. JObject o = ParseResponse(response);
  749. userID = (int)o["id"];
  750. Console.WriteLine("user id: " + userID);
  751. if (!SettingsManager.HasCloudIdentity(userID))
  752. SettingsManager.CloudIdentities.Add(new CloudIdentity { UserID = userID });
  753. RegisterDevice();
  754. }
  755. if (response != null) response.Close();
  756. };
  757. Thread cl_thread = new Thread(cloudThread);
  758. cl_thread.Name = "Cloud user thread";
  759. cl_thread.Priority = ThreadPriority.Lowest;
  760. cl_thread.Start();
  761. }
  762. /// <summary>
  763. /// Registers the device with the server
  764. /// </summary>
  765. private static void RegisterDevice()
  766. {
  767. ThreadStart cloudThread = delegate()
  768. {
  769. if (Identity != null && Identity.DeviceID > 0)
  770. {
  771. U.L(LogLevel.Debug, "SERVICE", "Device already registered, verifying ID");
  772. string url = String.Format("/devices/{0}.json", Identity.DeviceID);
  773. var response = SendRequest(url, "GET");
  774. if (response == null || response.StatusCode != HttpStatusCode.OK)
  775. {
  776. Identity.DeviceID = -1;
  777. U.L(LogLevel.Debug, "SERVICE", "Previous registered ID was invalid");
  778. RegisterDevice();
  779. }
  780. else
  781. {
  782. Stream s = response.GetResponseStream();
  783. StreamReader sr = new StreamReader(s);
  784. string str = sr.ReadToEnd();
  785. JObject o = JObject.Parse(str);
  786. string name = (string)o["name"];
  787. U.L(LogLevel.Debug, "SERVICE", "ID verified, device name is " + name);
  788. deviceName = name;
  789. DeviceName = name;
  790. DispatchPropertyChanged("Linked", false, true);
  791. RetrieveCloudConfigurations();
  792. }
  793. if (response != null) response.Close();
  794. }
  795. else
  796. {
  797. string name = U.Capitalize(Environment.MachineName);
  798. U.L(LogLevel.Debug, "SERVICE", "Need to register the device");
  799. string url = String.Format("/devices.json");
  800. string query = String.Format("?device[name]={0}&device[version]={1}",
  801. OAuth.Manager.UrlEncode(name),
  802. OAuth.Manager.UrlEncode(SettingsManager.Release));
  803. var response = SendRequest(url, "POST", query);
  804. if (response == null || response.StatusCode != HttpStatusCode.Created)
  805. {
  806. U.L(LogLevel.Error, "SERVICE", "There was a problem registering the device");
  807. U.L(LogLevel.Error, "SERVICE", response);
  808. Delink();
  809. }
  810. else
  811. {
  812. Stream s = response.GetResponseStream();
  813. StreamReader sr = new StreamReader(s);
  814. string str = sr.ReadToEnd();
  815. JObject o = JObject.Parse(str);
  816. Identity.DeviceID = (int)o["id"];
  817. U.L(LogLevel.Debug, "SERVICE", "Device has been registered with ID " + Identity.DeviceID.ToString());
  818. deviceName = name;
  819. DeviceName = name;
  820. DispatchPropertyChanged("Linked", false, true);
  821. RetrieveCloudConfigurations();
  822. }
  823. if (response != null) response.Close();
  824. }
  825. };
  826. Thread cl_thread = new Thread(cloudThread);
  827. cl_thread.Name = "Cloud register thread";
  828. cl_thread.Priority = ThreadPriority.Lowest;
  829. cl_thread.Start();
  830. }
  831. /// <summary>
  832. /// Retrieves all cloud configurations from the server.
  833. /// </summary>
  834. private static void RetrieveCloudConfigurations()
  835. {
  836. ThreadStart configThread = delegate()
  837. {
  838. U.L(LogLevel.Debug, "SERVICE", "Retrieving cloud configurations");
  839. string url = String.Format("/configurations.json");
  840. var response = SendRequest(url, "GET");
  841. if (response == null || response.StatusCode != HttpStatusCode.OK)
  842. {
  843. U.L(LogLevel.Error, "SERVICE", "There was a problem retrieving cloud configurations");
  844. U.L(LogLevel.Error, "SERVICE", response);
  845. }
  846. else
  847. {
  848. Stream s = response.GetResponseStream();
  849. StreamReader sr = new StreamReader(s);
  850. string str = sr.ReadToEnd();
  851. Console.WriteLine("response: " + str);
  852. JArray a = JArray.Parse(str);
  853. object old = SyncProfiles;
  854. SyncProfiles.Clear();
  855. foreach (JObject config in a)
  856. {
  857. string name = (string)config["name"];
  858. int id = (int)config["id"];
  859. if (SyncProfiles.ContainsKey(id))
  860. SyncProfiles[id] = name;
  861. else
  862. SyncProfiles.Add(id, name);
  863. }
  864. DispatchPropertyChanged("SyncProfiles", old, SyncProfiles as object, true);
  865. }
  866. if (response != null) response.Close();
  867. };
  868. Thread config_thread = new Thread(configThread);
  869. config_thread.Name = "Cloud configuration thread";
  870. config_thread.Priority = ThreadPriority.Lowest;
  871. config_thread.Start();
  872. }
  873. /// <summary>
  874. /// Will update Stoffi's current configuration according to the
  875. /// state of the synchronization profile.
  876. /// </summary>
  877. /// <param name="updatedParams">The updated parameters of the configuration.</param>
  878. private static void SyncProfileUpdated(JObject updatedParams)
  879. {
  880. SettingsManager.PropertyChanged -= SettingsManager_PropertyChanged;
  881. #region Media state
  882. if (updatedParams["media_state"] != null)
  883. {
  884. try
  885. {
  886. switch ((string)updatedParams["media_state"])
  887. {
  888. case "Playing":
  889. MediaManager.Play();
  890. break;
  891. case "Paused":
  892. Console.WriteLine("PAUSE!");
  893. MediaManager.Pause();
  894. break;
  895. case "Stopped":
  896. Console.WriteLine("STOP!");
  897. MediaManager.Stop();
  898. break;
  899. }
  900. }
  901. catch (Exception e)
  902. {
  903. U.L(LogLevel.Error, "SERVICE", "Problem synchronizing media state: " + e.Message);
  904. }
  905. }
  906. #endregion
  907. #region Shuffle
  908. if (updatedParams["shuffle"] != null)
  909. {
  910. try
  911. {
  912. switch ((string)updatedParams["shuffle"])
  913. {
  914. case "NoShuffle":
  915. SettingsManager.Shuffle = false;
  916. break;
  917. case "Random":
  918. SettingsManager.Shuffle = true;
  919. break;
  920. }
  921. }
  922. catch (Exception e)
  923. {
  924. U.L(LogLevel.Error, "SERVICE", "Problem synchronizing shuffle mode: " + e.Message);
  925. }
  926. }
  927. #endregion
  928. #region Repeat
  929. if (updatedParams["repeat"] != null)
  930. {
  931. try
  932. {
  933. switch ((string)updatedParams["repeat"])
  934. {
  935. case "NoRepeat":
  936. SettingsManager.Repeat = RepeatState.NoRepeat;
  937. break;
  938. case "RepeatAll":
  939. SettingsManager.Repeat = RepeatState.RepeatAll;
  940. break;
  941. case "RepeatOne":
  942. SettingsManager.Repeat = RepeatState.RepeatOne;
  943. break;
  944. }
  945. }
  946. catch (Exception e)
  947. {
  948. U.L(LogLevel.Error, "SERVICE", "Problem synchronizing repeat mode: " + e.Message);
  949. }
  950. }
  951. #endregion
  952. #region Volume
  953. if (updatedParams["volume"] != null)
  954. {
  955. try
  956. {
  957. float vol = (float)updatedParams["volume"];
  958. SettingsManager.Volume = Convert.ToDouble(vol);
  959. }
  960. catch (Exception e)
  961. {
  962. try
  963. {
  964. string vol = (string)updatedParams["volume"];
  965. SettingsManager.Volume = Convert.ToDouble(vol);
  966. }
  967. catch (Exception ee)
  968. {
  969. U.L(LogLevel.Error, "SERVICE", "Problem synchronizing volume");
  970. U.L(LogLevel.Error, "SERVICE", e.Message);
  971. U.L(LogLevel.Error, "SERVICE", ee.Message);
  972. }
  973. }
  974. }
  975. #endregion
  976. #region Current track
  977. if (updatedParams["current_track"] != null)
  978. {
  979. try
  980. {
  981. string path = (string)updatedParams["current_track"]["path"];
  982. if (path != null)
  983. {
  984. // TODO:
  985. // Create function SettingsManager.FindTrack()
  986. // It should then either find a local track fast
  987. // or convert the path into a youtube track.
  988. if (YouTubeManager.IsYouTube(path))
  989. {
  990. TrackData track = YouTubeManager.CreateTrack(YouTubeManager.GetYouTubeID(path));
  991. if (track != null)
  992. SettingsManager.CurrentTrack = track;
  993. }
  994. else
  995. {
  996. foreach (TrackData track in SettingsManager.FileTracks)
  997. {
  998. if (track.Path == path)
  999. {
  1000. SettingsManager.CurrentTrack = track;
  1001. break;
  1002. }
  1003. }
  1004. }
  1005. }
  1006. }
  1007. catch (Exception e)
  1008. {
  1009. U.L(LogLevel.Error, "SERVICE", "Could not read current track from server");
  1010. U.L(LogLevel.Error, "SERVICE", e.Message);
  1011. }
  1012. }
  1013. #endregion
  1014. SettingsManager.PropertyChanged += SettingsManager_PropertyChanged;
  1015. }
  1016. /// <summary>
  1017. /// Sends a request to the server
  1018. /// </summary>
  1019. /// <param name="url">The base url</param>
  1020. /// <param name="method">The HTTP method to use</param>
  1021. /// <param name="query">The query string, encoded properly</param>
  1022. /// <returns>The response from the server</returns>
  1023. private static HttpWebResponse SendRequest(string url, string method, string query = "")
  1024. {
  1025. if (SettingsManager.HasCloudIdentity(userID))
  1026. {
  1027. if (query == "") query = "?";
  1028. else query += "&";
  1029. query += "device_id=" + Identity.DeviceID.ToString();
  1030. }
  1031. string fullUrl = domain + url + query;
  1032. U.L(LogLevel.Debug, "SERVICE", "Sending " + method + " request to " + fullUrl);
  1033. var authzHeader = oauth.GenerateAuthzHeader(fullUrl, method);
  1034. var request = (HttpWebRequest)WebRequest.Create(fullUrl);
  1035. request.Method = method;
  1036. request.PreAuthenticate = true;
  1037. request.AllowWriteStreamBuffering = true;
  1038. request.Headers.Add("Authorization", authzHeader);
  1039. request.Timeout = 30000;
  1040. request.KeepAlive = false;
  1041. try
  1042. {
  1043. HttpWebResponse resp = (HttpWebResponse)request.GetResponse();
  1044. return resp;
  1045. }
  1046. catch (WebException exc)
  1047. {
  1048. U.L(LogLevel.Error, "SERVICE", "Error while contacting cloud server: " + exc.Message);
  1049. return null;
  1050. }
  1051. }
  1052. /// <summary>
  1053. /// Parses a web response encoded in JSON into an object.
  1054. /// </summary>
  1055. /// <param name="response">The JSON web response.</param>
  1056. /// <returns>A JSON object.</returns>
  1057. private static JObject ParseResponse(HttpWebResponse response)
  1058. {
  1059. try
  1060. {
  1061. Stream s = response.GetResponseStream();
  1062. StreamReader sr = new StreamReader(s);
  1063. string str = sr.ReadToEnd();
  1064. JObject o = JObject.Parse(str);
  1065. return o;
  1066. }
  1067. catch (Exception e)
  1068. {
  1069. U.L(LogLevel.Error, "SERVICE", "Could not parse response: " + e.Message);
  1070. return null;
  1071. }
  1072. }
  1073. /// <summary>
  1074. /// Encodes a track object into HTTP parameters.
  1075. /// </summary>
  1076. /// <param name="track">The track to encode</param>
  1077. /// <param name="prefix">An optional prefix to put on each property</param>
  1078. /// <returns>The track encoded as "title=X&artist=Y&genre=Z..."</returns>
  1079. private static List<string> EncodeTrack(TrackData track, string prefix = "")
  1080. {
  1081. List<string> paras = new List<string>();
  1082. if (track != null)
  1083. {
  1084. paras.Add(U.CreateParam("title", track.Title, prefix));
  1085. paras.Add(U.CreateParam("artist", track.Artist, prefix));
  1086. paras.Add(U.CreateParam("album", track.Album, prefix));
  1087. paras.Add(U.CreateParam("length", track.RawLength, prefix));
  1088. paras.Add(U.CreateParam("genre", track.Genre, prefix));
  1089. paras.Add(U.CreateParam("track", track.Track, prefix));
  1090. paras.Add(U.CreateParam("path", track.Path, prefix));
  1091. paras.Add(U.CreateParam("views", track.RawViews, prefix));
  1092. paras.Add(U.CreateParam("bitrate", track.Bitrate, prefix));
  1093. paras.Add(U.CreateParam("codecs", track.Codecs, prefix));
  1094. paras.Add(U.CreateParam("channels", track.Channels, prefix));
  1095. paras.Add(U.CreateParam("sample_rate", track.SampleRate, prefix));
  1096. paras.Add(U.CreateParam("year", track.Year, prefix));
  1097. }
  1098. return paras;
  1099. }
  1100. #endregion
  1101. #region Event handlers
  1102. /// <summary>
  1103. /// Invoked when a property of the settings manager is changed
  1104. /// </summary>
  1105. /// <param name="sender">The sender of the event</param>
  1106. /// <param name="e">The event data</param>
  1107. private static void SettingsManager_PropertyChanged(object sender, PropertyChangedWithValuesEventArgs e)
  1108. {
  1109. if (Identity == null || Identity.ConfigurationID < 1) return;
  1110. // create query string
  1111. switch (e.PropertyName)
  1112. {
  1113. case "Volume":
  1114. string vol = Convert.ToString(SettingsManager.Volume);
  1115. toBeSynced["configuration[volume]"] = OAuth.Manager.UrlEncode(vol);
  1116. break;
  1117. case "Repeat":
  1118. toBeSynced["configuration[repeat]"] = OAuth.Manager.UrlEncode(SettingsManager.Repeat.ToString());
  1119. break;
  1120. case "Shuffle":
  1121. toBeSynced["configuration[shuffle]"] = OAuth.Manager.UrlEncode(SettingsManager.Shuffle ? "Random" : "Off");
  1122. break;
  1123. case "MediaState":
  1124. toBeSynced["configuration[media_state]"] = OAuth.Manager.UrlEncode(SettingsManager.MediaState.ToString());
  1125. break;
  1126. case "CurrentTrack":
  1127. List<string> paras = EncodeTrack(SettingsManager.CurrentTrack, "configurations[current_track]");
  1128. foreach (string para in paras)
  1129. {
  1130. if (para != null)
  1131. {
  1132. string[] p = para.Split(new char[] { '=' }, 2);
  1133. if (p[0] != "" && p[1] != "")
  1134. toBeSynced[p[0]] = p[1];
  1135. }
  1136. }
  1137. break;
  1138. default:
  1139. return;
  1140. }
  1141. if (syncDelay != null)
  1142. syncDelay.Dispose();
  1143. syncDelay = new Timer(PerformSync, null, 400, 3600);
  1144. }
  1145. /// <summary>
  1146. /// Sends requests to the server updating the properties stored in
  1147. /// toBeSynced
  1148. /// </summary>
  1149. /// <param name="state">The state (not used)</param>
  1150. private static void PerformSync(object state)
  1151. {
  1152. syncDelay.Dispose();
  1153. syncDelay = null;
  1154. int id = Identity.ConfigurationID;
  1155. if (id < 0 || toBeSynced.Count == 0) return;
  1156. ThreadStart thread = delegate()
  1157. {
  1158. U.L(LogLevel.Debug, "SERVICE", "Sending parameters to sync server");
  1159. string url = String.Format("/configurations/{0}.json", id);
  1160. string query = "";
  1161. foreach (KeyValuePair<string, string> p in toBeSynced)
  1162. query += "&" + p.Key + "=" + p.Value;
  1163. query = "?" + query.Substring(1);
  1164. U.L(LogLevel.Debug, "SERVICE", "Updating cloud configuration with ID " + id);
  1165. // send put request to server
  1166. var response = SendRequest(url, "PUT", query);
  1167. if (response == null || response.StatusCode != HttpStatusCode.OK)
  1168. {
  1169. U.L(LogLevel.Error, "SERVICE", "There was a problem updating configuration");
  1170. U.L(LogLevel.Error, "SERVICE", response);
  1171. }
  1172. else
  1173. {
  1174. U.L(LogLevel.Debug, "SERVICE", "Configuration was updated successfully");
  1175. }
  1176. if (response != null) response.Close();
  1177. };
  1178. Thread th = new Thread(thread);
  1179. th.Name = "Sync thread";
  1180. th.Priority = ThreadPriority.Lowest;
  1181. th.Start();
  1182. }
  1183. #endregion
  1184. #region Dispatchers
  1185. /// <summary>
  1186. /// Trigger the PropertyChanged event
  1187. /// </summary>
  1188. /// <param name="name">The name of the property that was changed</param>
  1189. /// <param name="oldValue">The value of the property before the change</param>
  1190. /// <param name="newValue">The value of the property after the change</param>
  1191. /// <param name="force">Dispatch even when no change occured between values</param>
  1192. public static void DispatchPropertyChanged(string name, object oldValue, object newValue, bool force = false)
  1193. {
  1194. if (PropertyChanged != null && (oldValue != newValue || force))
  1195. PropertyChanged(null, new PropertyChangedWithValuesEventArgs(name, oldValue, newValue));
  1196. }
  1197. /// <summary>
  1198. /// Trigger the Initialized event.
  1199. /// </summary>
  1200. public static void DispatchInitialized()
  1201. {
  1202. if (Initialized != null)
  1203. Initialized(null, new EventArgs());
  1204. }
  1205. /// <summary>
  1206. /// Trigger the Disconnected event.
  1207. /// </summary>
  1208. public static void DispatchDisconnected()
  1209. {
  1210. if (Disconnected != null)
  1211. Disconnected(null, new EventArgs());
  1212. }
  1213. #endregion
  1214. #endregion
  1215. #region Events
  1216. /// <summary>
  1217. /// Occurs when a property has been changed
  1218. /// </summary>
  1219. public static event PropertyChangedWithValuesEventHandler PropertyChanged;
  1220. /// <summary>
  1221. /// Occurs when the service manager has been fully initialized.
  1222. /// </summary>
  1223. public static event EventHandler Initialized;
  1224. /// <summary>
  1225. /// Occurs when the service manager has been disconnected from service.
  1226. /// </summary>
  1227. public static event EventHandler Disconnected;
  1228. #endregion
  1229. }
  1230. /// <summary>
  1231. /// Describes the interface that the async javascript will call to update attributes
  1232. /// </summary>
  1233. [ComVisibleAttribute(true)]
  1234. public class CloudSyncInterface
  1235. {
  1236. /// <summary>
  1237. /// Invoked when an object changes.
  1238. /// </summary>
  1239. /// <param name="objectType">The name of the type of object</param>
  1240. /// <param name="objectID">The object ID</param>
  1241. /// <param name="properties">The updated properties encoded in JSON</param>
  1242. public void UpdateObject(string objectType, int objectID, string properties)
  1243. {
  1244. ServiceManager.UpdateObject(objectType, objectID, properties);
  1245. }
  1246. /// <summary>
  1247. /// Invoked when an object is created.
  1248. /// </summary>
  1249. /// <param name="objectType">The name of the type of object</param>
  1250. /// <param name="objectJSON">The object encoded as JSON</param>
  1251. public void CreateObject(string objectType, string objectJSON)
  1252. {
  1253. ServiceManager.CreateObject(objectType, objectJSON);
  1254. }
  1255. /// <summary>
  1256. /// Invoked when an object is deleted.
  1257. /// </summary>
  1258. /// <param name="objectType">The name of the type of object</param>
  1259. /// <param name="objectID">The object id</param>
  1260. public void DeleteObject(string objectType, int objectID)
  1261. {
  1262. ServiceManager.DeleteObject(objectType, objectID);
  1263. }
  1264. /// <summary>
  1265. /// Invoked when a command is to be executed.
  1266. /// </summary>
  1267. /// <param name="command">The name of the command</param>
  1268. /// <param name="configID">The ID of the config</param>
  1269. public void Execute(string command, string configID)
  1270. {
  1271. ServiceManager.ExecuteCommand(command, configID);
  1272. }
  1273. }
  1274. #region Delegates
  1275. #endregion
  1276. #region Event arguments
  1277. #endregion
  1278. #region Enums
  1279. #endregion
  1280. }