PageRenderTime 58ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/yaTDL/yaTDL.Core/Toodledo/ToodledoClientV2.cs

#
C# | 562 lines | 413 code | 140 blank | 9 comment | 67 complexity | 3d0f1a4b257b4d4a3ce607e414bc62bd MD5 | raw file
  1. //Copyright (c) 2010, Andreas Grünwald
  2. //All rights reserved.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Reflection;
  10. using System.Text;
  11. using System.Web;
  12. using System.Xml.Linq;
  13. using Newtonsoft.Json;
  14. using Newtonsoft.Json.Linq;
  15. using Slf;
  16. using yaTDL.Extensions;
  17. using System.Windows;
  18. using System.Diagnostics;
  19. using System.IO;
  20. using System.Security;
  21. namespace yaTDL.Core.Toodledo
  22. {
  23. //for Toodledeo API Documentation see http://api.toodledo.com/2/index.php
  24. public class ToodledoClientV2
  25. {
  26. static string toodledoBaseUrl = "http://api.toodledo.com/2/";
  27. static string appId = ""; //TODO: Insert AppId and AppToken
  28. static string appToken = "";
  29. static string appVersion = Assembly.GetExecutingAssembly().FullName.Split(',')[1].Substring(9).Replace(".", "");
  30. static string device = "Windows_Client";
  31. static string osVersion = Environment.OSVersion.Version.ToString().Replace(".", "");
  32. WebClient webClient = new WebClient();
  33. ILogger logger = LoggerService.GetLogger("ToodledoClient");
  34. public event EventHandler<ErrorEventArgsV2> ErrorOccurred;
  35. public DateTime FirstRequest { get; set; }
  36. public ToodledoClientV2()
  37. {
  38. FirstRequest = DateTime.Parse("01.01.0001");
  39. }
  40. public string GenerateSignature(string userId)
  41. {
  42. return (userId + appToken).GetMD5Hash();
  43. }
  44. public string GetSessionToken(string userId)
  45. {
  46. string url = String.Format("{0}account/token.php?userid={1};appid={2};vers={3};device={4};os={5};sig={6};f=xml",
  47. toodledoBaseUrl, userId, appId, appVersion, device, osVersion,GenerateSignature(userId));
  48. XElement response = makeRequest(url);
  49. if (response.Name == "token") return response.Value;
  50. else return null;
  51. }
  52. public ToodledoAPIKey GetKey(string sessionToken, SecureString password)
  53. {
  54. ToodledoAPIKey key = new ToodledoAPIKey();
  55. key.Key = (password.Unwrap().GetMD5Hash() + appToken + sessionToken).GetMD5Hash();
  56. key.Expires = DateTime.Now.AddHours(3.9);
  57. return key;
  58. }
  59. public ToodledoAccountInfoV2 GetAccountInfo(string key)
  60. {
  61. string url = toodledoBaseUrl + "account/get.php?key=" + key + ";f=xml";
  62. XElement response = makeRequest(url);
  63. if (response.Name == "account")
  64. {
  65. ToodledoAccountInfoV2 accountInfo = new ToodledoAccountInfoV2();
  66. accountInfo.UserId = response.Element("userid").Value;
  67. accountInfo.Alias = response.Element("alias").Value;
  68. if (response.Element("pro").Value == "1") accountInfo.IsProUser = true;
  69. accountInfo.DateFormat = Int32.Parse(response.Element("dateformat").Value);
  70. accountInfo.TimeZone = Int32.Parse(response.Element("timezone").Value);
  71. accountInfo.HideMonths = Int32.Parse(response.Element("hidemonths").Value);
  72. accountInfo.HotListPriority = Int32.Parse(response.Element("hotlistpriority").Value);
  73. accountInfo.HotListDueDate = Int32.Parse(response.Element("hotlistduedate").Value);
  74. accountInfo.ShowTabNums = Int32.Parse(response.Element("showtabnums").Value);
  75. accountInfo.LastFolderEdit = (double.Parse(response.Element("lastedit_folder").Value)).ToDateTime();
  76. accountInfo.LastContextEdit = (double.Parse(response.Element("lastedit_context").Value)).ToDateTime();
  77. accountInfo.LastGoalEdit = (double.Parse(response.Element("lastedit_goal").Value)).ToDateTime();
  78. accountInfo.LastTaskEdit = (double.Parse(response.Element("lastedit_task").Value)).ToDateTime();
  79. accountInfo.LastTaskDelete = (double.Parse(response.Element("lastdelete_task").Value)).ToDateTime();
  80. return accountInfo;
  81. }
  82. else
  83. { return null; }
  84. }
  85. public Collection<Project> GetFolders(string key)
  86. {
  87. Collection<Project> projects = new Collection<Project>();
  88. string url = toodledoBaseUrl + "/folders/get.php?key=" + key + ";f=xml";
  89. XElement xElement = makeRequest(url);
  90. foreach (XElement x in xElement.Elements("folder"))
  91. {
  92. Project p = new Project();
  93. p.Name = x.Element("name").Value;
  94. p.Id = x.Element("id").Value;
  95. p.Order = Int32.Parse(x.Element("order").Value);
  96. projects.Add(p);
  97. }
  98. return projects;
  99. }
  100. public bool AddFolder(string key, Project project)
  101. {
  102. string url = String.Format("{0}folders/add.php?name={1};key={2};f=xml", toodledoBaseUrl, project.Name, key);
  103. XElement xElement = makeRequest(url);
  104. if (xElement.Name == "folders")
  105. {
  106. project.Id = xElement.Element("folder").Element("id").Value;
  107. project.Order = Int32.Parse(xElement.Element("folder").Element("order").Value);
  108. return true;
  109. }
  110. else
  111. { return false; }
  112. }
  113. public bool UpdateFolder(string key, Project project)
  114. {
  115. string url = String.Format("{0}folders/edit.php?id={1};name={2};key={3};f=xml", toodledoBaseUrl, project.Id, project.Name, key);
  116. return makeRequest(url).Name == "folders" ? true : false;
  117. }
  118. public bool DeleteFolder(string key, int id)
  119. {
  120. string url = String.Format("{0}folders/delete.php?id={1};key={2};f=xml", toodledoBaseUrl, id, key);
  121. return makeRequest(url).Name == "deleted" ? true : false;
  122. }
  123. public Collection<Task> GetTasks(string key, List<Parameter> parameters)
  124. {
  125. string url = String.Format("{0}tasks/get.php?key={1};", toodledoBaseUrl, key);
  126. foreach (Parameter parameter in parameters)
  127. {
  128. url += parameter.Name + "=";
  129. switch (parameter.Name.ToLower())
  130. {
  131. case "id":
  132. case "start":
  133. case "num":
  134. url += parameter.Value.ToString();
  135. break;
  136. case "modbefore":
  137. case "modafter":
  138. url += ((DateTime)parameter.Value).ToUnixTime().ToString();
  139. break;
  140. case "comp":
  141. if (parameter.Value.GetType() == typeof(int))
  142. {
  143. url += ((int)parameter.Value).ToString();
  144. }
  145. if (parameter.Value.GetType() == typeof(bool))
  146. {
  147. if ((bool)parameter.Value) { url += "1"; }
  148. else { url += "0"; }
  149. }
  150. break;
  151. }
  152. url += ";";
  153. }
  154. url += "fields=folder,priority,duedate,repeat,repeatfrom,added;f=xml";
  155. Collection<Task> tasks = new Collection<Task>();
  156. XElement response = makeRequest(url);
  157. if (response.Name == "tasks")
  158. {
  159. foreach (XElement item in response.Elements("task"))
  160. {
  161. Task t = new Task();
  162. t.Id = item.Element("id").Value;
  163. t.Content = urlDecode(item.Element("title").Value);
  164. t.ProjectId = item.Element("folder").Value;
  165. if (String.IsNullOrEmpty(item.Element("modified").Value))
  166. {
  167. t.LastEdited = (double.Parse(item.Element("added").Value)).ToDateTime();
  168. }
  169. else
  170. {
  171. t.LastEdited = (double.Parse(item.Element("modified").Value)).ToDateTime();
  172. }
  173. if (String.IsNullOrEmpty(item.Element("duedate").Value) || item.Element("duedate").Value == "0")
  174. { t.DueDate = DateTime.Parse("01.01.0001"); }
  175. else
  176. {
  177. t.DueDate = (double.Parse(item.Element("duedate").Value)).ToDateTime().ToLocalTime();
  178. }
  179. if (String.IsNullOrEmpty(item.Element("completed").Value) || item.Element("completed").Value == "0")
  180. { t.Completed = false; }
  181. else
  182. { t.Completed = true; }
  183. t.Recurrence.AdvancedRepeat = item.Element("repeat").Value;
  184. t.Recurrence.Repeat = repeatFromToRepeat(Int32.Parse(item.Element("repeatfrom").Value), t.Recurrence.AdvancedRepeat);
  185. t.Priority = Int32.Parse(item.Element("priority").Value);
  186. tasks.Add(t);
  187. }
  188. }
  189. return tasks;
  190. }
  191. public bool AddTasks(string key, List<Task> tasks)
  192. {
  193. if (tasks.Count > 20)
  194. {
  195. List<List<Task>> taskLists = splitTaskList(20, tasks);
  196. List<bool> results = new List<bool>();
  197. foreach (List<Task> taskList in taskLists)
  198. {
  199. results.Add(AddTasks(key, taskList));
  200. }
  201. return results.Any(x=> x== false) ? false : true;
  202. }
  203. string serializedTasks = jsonSerialize(tasks, includeId:false, addIdAsMeta:true);
  204. if (!String.IsNullOrEmpty(serializedTasks))
  205. {
  206. string url = String.Format("{0}tasks/add.php?key={1};tasks={2};fields=folder,priority,duedate,repeat,meta;f=xml",
  207. toodledoBaseUrl, key, serializedTasks);
  208. XElement response = makeRequest(url);
  209. if (response.Name == "tasks")
  210. {
  211. foreach (XElement element in response.Elements("task"))
  212. {
  213. var matchingTasks = from task in tasks
  214. where task.Id == XElement.Parse(element.Element("meta").Value).Element("localId_original").Value
  215. select task;
  216. //var matchingTasks = from task in tasks
  217. // where task.Content == element.Element("title").Value
  218. // && task.ProjectId == element.Element("folder").Value
  219. // && task.Recurrence.AdvancedRepeat == element.Element("repeat").Value
  220. // && task.Recurrence.Repeat == repeatFromToRepeat(Int32.Parse(element.Element("repeatfrom").Value), element.Element("repeat").Value)
  221. // select task;
  222. if (matchingTasks.Any())
  223. {
  224. if (matchingTasks.Count() > 1)
  225. {
  226. Core.UI.Dialogs.ShowMessageBox("AddTasks: Found more than one matching Task. This should not have happend");
  227. logger.Error("AddTasks - Found more than one matching Task");
  228. return false;
  229. }
  230. matchingTasks.First().Id = element.Element("id").Value;
  231. }
  232. else
  233. {
  234. logger.Error("AddTasks: response did not contain a matching Task");
  235. return false;
  236. }
  237. }
  238. return true;
  239. }
  240. }
  241. return false;
  242. }
  243. public bool UpdateTasks(string key, List<Task> tasks)
  244. {
  245. if (tasks.Count > 25)
  246. {
  247. List<List<Task>> taskLists = splitTaskList(25, tasks);
  248. List<bool> results = new List<bool>();
  249. foreach (List<Task> taskList in taskLists)
  250. {
  251. results.Add(UpdateTasks(key, taskList));
  252. }
  253. var fails = from result in results
  254. where result == false
  255. select result;
  256. return fails.Any() ? false : true;
  257. }
  258. string url = String.Format("{0}tasks/edit.php?key={1};tasks={2};f=xml", toodledoBaseUrl, key, jsonSerialize(tasks, true));
  259. XElement response = makeRequest(url);
  260. if (response.Name == "tasks")
  261. return true;
  262. else
  263. return false;
  264. }
  265. public bool DeleteTasks(string key, List<int> ids)
  266. {
  267. string url = String.Format("{0}tasks/delete.php?key={1};tasks={2};f=xml", toodledoBaseUrl, key, JsonConvert.SerializeObject(ids.ToArray()).Replace("&", "%2C"));
  268. XElement response = makeRequest(url);
  269. return response.Name == "deleted" ? true : false;
  270. }
  271. public Collection<string> GetDeletedTasks(string key, DateTime after)
  272. {
  273. string url = String.Format("{0}tasks/deleted.php?key={1};after={2};f=xml", toodledoBaseUrl, key, after.ToUnixTime().ToString());
  274. XElement response = makeRequest(url);
  275. var _deleted = from task in response.Elements("task")
  276. select task.Element("id").Value;
  277. return new Collection<string>(_deleted.ToList<string>());
  278. }
  279. private void OnErrorOccurred(int errorCode, string errorMessage)
  280. {
  281. if (ErrorOccurred != null)
  282. {
  283. ErrorOccurred(this, new ErrorEventArgsV2() { ErrorCode = errorCode, ErrorMessage= errorMessage });
  284. }
  285. }
  286. int errorCounter = 1;
  287. private XElement makeRequest(string url)
  288. {
  289. if (FirstRequest == DateTime.Parse("01.01.0001")) { FirstRequest = DateTime.Now; }
  290. string responseString = webClient.DownloadString(url);
  291. XElement response = XElement.Parse(responseString);
  292. List<XElement> errors = response.DescendantsAndSelf("error").ToList<XElement>();
  293. foreach (XElement error in errors)
  294. {
  295. OnErrorOccurred(errorCode: Int32.Parse(error.Attribute("id").Value), errorMessage: error.Value);
  296. #if DEBUG
  297. if (error.Value.ToLower() == "no title")
  298. {
  299. logger.Error("url: {0}", url);
  300. File.WriteAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "noTitleErrorUrl_" + errorCounter), url);
  301. errorCounter++;
  302. }
  303. #endif
  304. }
  305. return response;
  306. }
  307. private static List<List<Task>> splitTaskList(int maxLength, List<Task> longList)
  308. {
  309. List<List<Task>> result = new List<List<Task>>();
  310. List<Task> currentList = new List<Task>();
  311. while (longList.Count > maxLength)
  312. {
  313. if (currentList.Count < maxLength)
  314. {
  315. currentList.Add(longList.First());
  316. longList.RemoveAt(0);
  317. }
  318. else
  319. {
  320. result.Add(currentList);
  321. currentList = new List<Task>();
  322. }
  323. }
  324. result.Add(longList);
  325. return result;
  326. }
  327. [DebuggerStepThrough()]
  328. private static string jsonSerialize(List<Task> tasks, bool includeId, bool addIdAsMeta = false)
  329. {
  330. string result = "[";
  331. if (tasks.Count == 1)
  332. { result += jsonSerialize(tasks.First(), includeId,addIdAsMeta) + "]"; }
  333. else
  334. {
  335. foreach (Task t in tasks)
  336. {
  337. result += jsonSerialize(t, includeId, addIdAsMeta);
  338. if (t != tasks.Last())
  339. {
  340. result += "%2C";
  341. }
  342. }
  343. result += "]";
  344. }
  345. return result;
  346. }
  347. [DebuggerStepThrough()]
  348. private static string jsonSerialize(Task task, bool includeId, bool addIdAsMeta)
  349. {
  350. if (task == null) return "";
  351. if (String.IsNullOrEmpty(task.Content))
  352. {
  353. throw new Exception();
  354. }
  355. string baseString = "%2C\"{0}\"%3A\"{1}\"";
  356. string result = "{";
  357. if (includeId)
  358. {
  359. result += String.Format("\"id\"%3A\"{0}\"", task.Id);
  360. result += String.Format(baseString, "title", task.Content);
  361. }
  362. else
  363. {
  364. result += String.Format("\"title\"%3A\"{0}\"", task.Content);
  365. }
  366. result += String.Format(baseString, "priority", task.Priority);
  367. result += String.Format(baseString, "repeat", task.Recurrence.Repeat);
  368. if (task.ProjectId != "0")
  369. result += String.Format(baseString, "folder", task.ProjectId);
  370. if (task.DueDate.Date != DateTime.Parse("01.01.0001").Date)
  371. result += String.Format(baseString, "duedate", task.DueDate.ToUnixTime());
  372. if (task.Completed)
  373. result += String.Format(baseString, "completed", task.LastEdited.ToUnixTime());
  374. else
  375. result += String.Format(baseString, "completed", "0");
  376. if (addIdAsMeta)
  377. {
  378. XElement meta = new XElement("meta");
  379. meta.Add(new XElement("localId_original") { Value = task.Id });
  380. result += String.Format(baseString, "meta", meta.ToString().Replace("\r\n", ""));
  381. }
  382. result += "}";
  383. return result;
  384. }
  385. private static int repeatFromToRepeat(int repeatFrom, string advancedRepeat)
  386. {
  387. if (String.IsNullOrEmpty(advancedRepeat))
  388. {
  389. return 0;
  390. }
  391. else
  392. {
  393. return repeatFrom == 0 ? 50 : 150;
  394. }
  395. }
  396. private static bool isSameDueDate(DateTime localDueDate, double toodledoDueDate)
  397. {
  398. if (toodledoDueDate == 0 && localDueDate.Date == DateTime.Parse("01.01.0001"))
  399. return true;
  400. else
  401. return false;
  402. }
  403. private static string urlDecode(string content)
  404. {
  405. string result = HttpUtility.UrlDecode(content);
  406. result = result.Replace("ö", "ö");
  407. result = result.Replace("€", "€");
  408. result = result.Replace("ä", "ä");
  409. result = result.Replace("Ä", "Ä");
  410. result = result.Replace("ü", "ü");
  411. result = result.Replace("ß", "ß");
  412. result = result.Replace("Ö", "Ö");
  413. result = result.Replace("Ãœ", "Ü");
  414. return result;
  415. }
  416. }
  417. public class Parameter
  418. {
  419. public string Name { get; set; }
  420. public object Value { get; set; }
  421. }
  422. }