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