PageRenderTime 63ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/GoogleVoice/ContactsManager.cs

https://github.com/davuxcom/GoogleVoice
C# | 479 lines | 407 code | 54 blank | 18 comment | 40 complexity | bd7eeda59faa8785aeedeba3ec7930bf MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Xml.Serialization;
  10. using System.Xml.Linq;
  11. using System.Text.RegularExpressions;
  12. using Newtonsoft.Json.Linq;
  13. using System.Globalization;
  14. namespace GoogleVoice
  15. {
  16. public class Contact : INotifyPropertyChanged
  17. {
  18. private string _Name = "";
  19. public string Name
  20. {
  21. get { return _Name; }
  22. set
  23. {
  24. if (value != _Name)
  25. {
  26. _Name = value;
  27. Changed("Name");
  28. }
  29. }
  30. }
  31. public string ID { get; set; }
  32. public string ImageETag { get; set; }
  33. public List<Phone> Phones = new List<Phone>();
  34. private string _ImageLocation = "";
  35. public string ImageLocation
  36. {
  37. get { return _ImageLocation; }
  38. set
  39. {
  40. if (value != _ImageLocation)
  41. {
  42. _ImageLocation = value;
  43. Changed("ImageLocation");
  44. }
  45. }
  46. }
  47. private string _Group = "";
  48. public string Group
  49. {
  50. get { return _Group; }
  51. set
  52. {
  53. if (value != _Group)
  54. {
  55. _Group = value;
  56. Changed("Group");
  57. }
  58. }
  59. }
  60. private static int _MaxIconSize = 0;
  61. public static int MaxIconSize
  62. {
  63. get
  64. {
  65. return _MaxIconSize;
  66. }
  67. set
  68. {
  69. if (_MaxIconSize != value)
  70. {
  71. _MaxIconSize = value;
  72. //Changed("MaxIconSize");
  73. }
  74. }
  75. }
  76. public Contact()
  77. {
  78. Group = "Other Contacts";
  79. }
  80. public override string ToString()
  81. {
  82. return Name;
  83. }
  84. public bool HasNumber(string number)
  85. {
  86. return Phones.Exists(p => Util.CompareNumber(p.Number, number));
  87. }
  88. private void Changed(string property)
  89. {
  90. if (PropertyChanged != null)
  91. PropertyChanged(this, new PropertyChangedEventArgs(property));
  92. }
  93. public event PropertyChangedEventHandler PropertyChanged;
  94. public string Note { get; set; }
  95. }
  96. public class Phone
  97. {
  98. public string Number { get; set; }
  99. public string Type { get; set; }
  100. public override string ToString()
  101. {
  102. return Util.FormatNumber(Number) + " (" + Type + ")";
  103. }
  104. }
  105. class ContactComparer : IComparer<Contact>
  106. {
  107. public int Compare(Contact c1, Contact c2)
  108. {
  109. if (c1 != null && c2 != null)
  110. {
  111. return c2.Name.CompareTo(c1.Name);
  112. }
  113. return 1;
  114. }
  115. }
  116. public class ContactsManager
  117. {
  118. public GVObservableCollectionEx<Contact> Contacts = null;
  119. public delegate void ProgressUpdate(int progressValue, int maxValue);
  120. public event ProgressUpdate ContactsLoadingUpdate;
  121. private string ImageDir = "";
  122. private string UserName = "";
  123. private HttpSession http { get; set; }
  124. // TODO this should be fixed along with Cache_Dir indicating things
  125. public bool LoadImages = true;
  126. public event Action OnContactsUpdated;
  127. internal ContactsManager(string ImageDir, string UserName, HttpSession http)
  128. {
  129. ContactsLoadingUpdate = delegate { }; // suppress warning
  130. this.UserName = UserName;
  131. this.ImageDir = ImageDir;
  132. this.http = http;
  133. try
  134. {
  135. var memoryStream = new FileStream(ImageDir + "\\" + UserName + "_contacts.xml", FileMode.OpenOrCreate);
  136. Contacts = new GVObservableCollectionEx<Contact>((List<Contact>)new XmlSerializer(typeof(List<Contact>)).Deserialize(memoryStream));
  137. Contacts.Sort(new ContactComparer());
  138. }
  139. catch (Exception ex)
  140. {
  141. Contacts = new GVObservableCollectionEx<Contact>();
  142. Trace.WriteLine("GoogleVoice/ContactsManager/ " + ex);
  143. }
  144. }
  145. public void Save()
  146. {
  147. try
  148. {
  149. MemoryStream memoryStream = new MemoryStream();
  150. System.Xml.XmlTextWriter xmlTextWriter = new System.Xml.XmlTextWriter(memoryStream, Encoding.UTF8);
  151. new XmlSerializer(typeof(List<Contact>)).Serialize(xmlTextWriter, (object)Contacts.ToList());
  152. memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
  153. File.WriteAllText(ImageDir + "\\" + UserName + "_contacts.xml", new UTF8Encoding().GetString(memoryStream.ToArray()));
  154. }
  155. catch (Exception ex)
  156. {
  157. Trace.WriteLine("GoogleVoice/ContactsManager/SaveXml " + ex);
  158. }
  159. }
  160. public void Update()
  161. {
  162. try
  163. {
  164. List<Contact> OldContacts = new List<Contact>(Contacts);
  165. HttpResult ret = http.Get("https://www.google.com/voice?ui=desktop");
  166. var m = Regex.Match(ret.Page, @"_gcData.*?\=(.*?)_gvRun", RegexOptions.Singleline);
  167. if (m.Success)
  168. {
  169. // hack: :(
  170. var json = m.Groups[1].Value.Replace('\'', '"');
  171. json = Regex.Replace(json, "\"flags\":\\s*{\\s*};", "", RegexOptions.Singleline);
  172. var o = JObject.Parse(json);
  173. var contacts = o["contacts"];
  174. string BasePhotoUrl = "http://www.google.com/s2/b/0"; // (Body["UserData"]["PhotoUrl"] as JValue).Value.ToString();
  175. Trace.WriteLine("Base Photo URL: " + BasePhotoUrl);
  176. // We'll fail on any bad contact, because we want to FailFast here
  177. // that way, the user will not get any contacts, and hopefully report
  178. // the bug to us.
  179. foreach (var cx in contacts)
  180. {
  181. var contact = cx.First;
  182. Trace.WriteLine(contact);
  183. string ID = (contact["contactId"] as JValue).Value.ToString();
  184. string Name = (contact["name"] as JValue).Value.ToString();
  185. bool shouldAdd = false;
  186. Contact c = Contacts.SingleOrDefault(x => x.ID == ID);
  187. if (c == null)
  188. {
  189. c = new Contact();
  190. shouldAdd = true;
  191. }
  192. else
  193. {
  194. OldContacts.Remove(c);
  195. }
  196. c.ID = ID;
  197. c.Name = WebUtil.HtmlDecode(Name);
  198. if (c.Name.Contains("Microsoft"))
  199. {
  200. // Debugger.Break();
  201. }
  202. c.Phones.Clear(); // kill old phones.
  203. foreach (var ph in (JArray)contact["numbers"])
  204. {
  205. try
  206. {
  207. if (ph.ToString().Contains("phoneType"))
  208. {
  209. c.Phones.Add(new Phone
  210. {
  211. Number = (ph["phoneNumber"] as JValue).Value.ToString(),
  212. Type = (ph["phoneType"] as JValue).Value.ToString()
  213. });
  214. }
  215. else
  216. {
  217. // NOTE: 5/5/2012
  218. // Contacts with 'Custom' label don't have a phoneType
  219. c.Phones.Add(new Phone
  220. {
  221. Number = (ph["phoneNumber"] as JValue).Value.ToString(),
  222. Type = "Unknown",
  223. });
  224. }
  225. }
  226. catch (NullReferenceException)
  227. {
  228. Debugger.Break();
  229. // no phoneType = GV number we should ignore
  230. }
  231. }
  232. if (LoadImages)
  233. {
  234. try
  235. {
  236. string photoUrl = (contact["photoUrl"] as JValue).Value.ToString();
  237. var r_ImgID = Regex.Match(photoUrl, ".*/(.*?)$", RegexOptions.Singleline);
  238. string ImgID = "";
  239. if (!r_ImgID.Success)
  240. {
  241. ImgID = Util.SafeFileName(photoUrl);
  242. }
  243. else
  244. {
  245. ImgID = Util.SafeFileName(r_ImgID.Groups[1].Value);
  246. }
  247. if (!string.IsNullOrEmpty(ImgID))
  248. {
  249. string save = ImageDir + ImgID + ".jpg";
  250. try
  251. {
  252. if (!File.Exists(save))
  253. {
  254. var imageBytes = http.GetFile(BasePhotoUrl + photoUrl);
  255. Trace.WriteLine("Saving: " + save);
  256. File.WriteAllBytes(save, imageBytes);
  257. }
  258. }
  259. catch (Exception ex)
  260. {
  261. // if we fail to save, we don't want to attempt it every time the contacts sync
  262. // so we'll just save anyway.
  263. // TODO consider failed=true property on contact download
  264. Trace.WriteLine("GoogleVoice/ContactsManager/Update/Photo Save Error: " + ex.Message);
  265. }
  266. c.ImageLocation = save;
  267. c.ImageETag = photoUrl;
  268. }
  269. }
  270. catch (Exception ex)
  271. {
  272. Trace.WriteLine("Photo: " + ex.Message);
  273. }
  274. }
  275. if (shouldAdd) Contacts.Add(c);
  276. }
  277. foreach (Contact deletedContact in OldContacts)
  278. {
  279. Trace.WriteLine("Removing orphaned contact: " + deletedContact);
  280. Contacts.Remove(deletedContact);
  281. }
  282. Contacts.Sort(new ContactComparer());
  283. if (OnContactsUpdated != null) OnContactsUpdated();
  284. }
  285. }
  286. catch (Exception ex)
  287. {
  288. Trace.WriteLine("Error updating contacts: " + ex);
  289. }
  290. }
  291. // Export system
  292. class CsvContact
  293. {
  294. string[] PhoneKeys = {
  295. "Primary Phone",
  296. "Home Phone",
  297. "Home Phone 2",
  298. "Mobile Phone",
  299. "Pager",
  300. "Home Fax",
  301. "Company Main Phone",
  302. "Business Phone",
  303. "Business Phone 2",
  304. "Business Fax",
  305. "Assistant's Phone",
  306. "Other Phone",
  307. "Other Fax",
  308. "Callback",
  309. "Car Phone",
  310. "ISDN",
  311. "Radio Phone",
  312. "TTY/TDD Phone",
  313. "Telex",
  314. };
  315. public class CsvPhone
  316. {
  317. public string Number { get; set; }
  318. public string Type { get; set; }
  319. }
  320. // First Name + Last Name
  321. // Company
  322. public string Name { get; set; }
  323. public string ID
  324. {
  325. get { return Name.GetHashCode().ToString(); }
  326. }
  327. public List<CsvPhone> Phones { get; set; }
  328. public CsvContact(string[] cols, string[] row)
  329. {
  330. Name = row[Lookup(cols, "First Name")] + " " + row[Lookup(cols, "Last Name")];
  331. Name = Name.Trim();
  332. if (string.IsNullOrEmpty(Name.Trim()))
  333. {
  334. Name = row[Lookup(cols, "Company")].Trim();
  335. if (string.IsNullOrEmpty(Name.Trim()))
  336. {
  337. throw new InvalidDataException("Bad contact name");
  338. }
  339. }
  340. Phones = new List<CsvPhone>();
  341. foreach (var type in PhoneKeys)
  342. {
  343. string num = row[Lookup(cols, type)];
  344. if (!string.IsNullOrEmpty(num.Trim()))
  345. {
  346. Phones.Add(new CsvPhone { Number = num, Type = type });
  347. }
  348. }
  349. }
  350. private int Lookup(string[] cols, string key)
  351. {
  352. for (int i = 0; i < cols.Length; i++)
  353. {
  354. if (cols[i].Trim().ToLower() == key.ToLower())
  355. {
  356. return i;
  357. }
  358. }
  359. throw new KeyNotFoundException(key);
  360. }
  361. public void AddPhone(CsvPhone newPhone)
  362. {
  363. bool found = false;
  364. foreach (var existingPhone in Phones)
  365. {
  366. if (Util.StripNumber(newPhone.Number) == Util.StripNumber(existingPhone.Number))
  367. {
  368. found = true;
  369. break;
  370. }
  371. }
  372. if (!found)
  373. {
  374. Phones.Add(newPhone);
  375. }
  376. }
  377. public void AddPhones(List<CsvPhone> phones)
  378. {
  379. foreach (var phone in phones)
  380. {
  381. AddPhone(phone);
  382. }
  383. }
  384. }
  385. List<CsvContact> GetContacts(string contactsData)
  386. {
  387. var ms = new MemoryStream();
  388. var stringBytes = System.Text.Encoding.UTF8.GetBytes(contactsData);
  389. ms.Write(stringBytes, 0, stringBytes.Length);
  390. ms.Seek(0, SeekOrigin.Begin);
  391. var parser = new Microsoft.VisualBasic.FileIO.TextFieldParser(ms);
  392. parser.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
  393. parser.SetDelimiters(new string[] { "," });
  394. string[] cols = parser.ReadFields();
  395. List<CsvContact> contacts = new List<CsvContact>();
  396. while (!parser.EndOfData)
  397. {
  398. string[] row = parser.ReadFields();
  399. try
  400. {
  401. CsvContact contact = new CsvContact(cols, row);
  402. var existingEntry = contacts.FirstOrDefault(c => c.ID == contact.ID);
  403. if (existingEntry != null)
  404. {
  405. // merge instead of adding
  406. existingEntry.AddPhones(contact.Phones);
  407. }
  408. else
  409. {
  410. contacts.Add(contact);
  411. }
  412. }
  413. catch (InvalidDataException)
  414. {
  415. // Contact doesn't have a good enough name
  416. }
  417. }
  418. return contacts;
  419. }
  420. }
  421. }