Merge lp:~igors/do-plugins/Thunderbird-fix into lp:do-plugins

Proposed by Igor Slepchin
Status: Merged
Merged at revision: 757
Proposed branch: lp:~igors/do-plugins/Thunderbird-fix
Merge into: lp:do-plugins
Diff against target: 367 lines (+213/-44)
3 files modified
Thunderbird/Makefile.am (+2/-1)
Thunderbird/Thunderbird.mdp (+1/-0)
Thunderbird/src/ThunderbirdContactItemSource.cs (+210/-43)
To merge this branch: bzr merge lp:~igors/do-plugins/Thunderbird-fix
Reviewer Review Type Date Requested Status
Chris Halse Rogers Needs Information
Review via email: mp+17102@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Igor Slepchin (igors) wrote :

This fixes Thunderbird plugin. The original problem was described in https://bugs.launchpad.net/do-plugins/+bug/305778 and was fixed by the first commit on the branch.

The rest of the changes add the following:

 * Support for multiple Thunderbird emails on a single ContactItem (ContactDetailItems are introduced)
 * Importing emails from Thunderbird history in addition to Thunderbird address book
 * Using the most used email for main ContactItem (as determined by Thunderbird's PopularityIndex)

I know the plugin is marked as "Community" but people still seem to distribute it (e.g., Fedora does).

Revision history for this message
Chris Halse Rogers (raof) wrote :

Bah. Sometimes, I hate bzr.

This branch looks like it's got some valuable improvements, but I can't actually pull it because of a repository version mismatch! If you could hit the “upgrade” button on your branch, it should convert it to a 2a repository, which I'll be able to pull.

It looks good, with the exception of the EmailList class. I'm not sure why you don't just pass a Dictionary<string, uint> around directly? The only value of EmailList appears to be .Add, which is only called once, and looks like it could replace with TryGetValue?

review: Needs Information
Revision history for this message
Igor Slepchin (igors) wrote :

Hey Chris - I clicked "upgrade" half a dozen times now but I'm not sure it's changed anything (upgrade button is still available and the repo is still marked as RepositoryFormatKnitPack6RichRoot (bzr 1.9)). Please let me know if this still does not work for you and I'll try to do the merge to the trunk locally or just re-publish it as a new branch (I still have the branch sources that I seem to be able to work with).

As for EmailList class, the primary motivation for it was overwriting Add method so that I didn't have to check if the key was already present in the Dictionary whenever I needed to update "popularity" value.

Revision history for this message
Chris Halse Rogers (raof) wrote :

Bah. No, it hasn't upgraded, so I'm still unable to pull from it.

Running “bzr upgrade --2a lp:~igors/do-plugins/Thunderbird-fix” might upgrade it properly, or you could (hopefully) push a new branch.

Ah. Somehow I didn't notice all the .Adds when doing my cursory review. I still find the class a bit of a heavyweight solution, so I might play around with some other options. I don't expect you to need to do any work there, though.

Revision history for this message
Igor Slepchin (igors) wrote :

bzr upgrade apparently worked, thanks!

I also merged this to the current trunk and re-pushed to https://code.launchpad.net/~igors/do-plugins/Thunderbird-fix-redux just in case so please let me know if either of these works for you.

Revision history for this message
Chris Halse Rogers (raof) wrote :

Excellent. That works.

I'll perform a more thorough review and merge.

lp:~igors/do-plugins/Thunderbird-fix updated
687. By Igor on 2011-02-08

Strip leading/trailing spaces and quotes from display names so that
multiple Thunderbird contacts get merged better.

Revision history for this message
Igor Slepchin (igors) wrote :

While I have your attention: do you think you could commit the fix for bug 698078 while at it? Thunderbird plugin is fairly useless if the email action itself does not work :p

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Thunderbird/Makefile.am'
--- Thunderbird/Makefile.am 2009-01-15 06:27:03 +0000
+++ Thunderbird/Makefile.am 2011-02-08 03:44:13 +0000
@@ -11,4 +11,5 @@
1111
12REFERENCES = \12REFERENCES = \
13 System \13 System \
14 $(DO_UNIVERSE_LIBS)14 $(DO_UNIVERSE_LIBS) \
15 $(DO_PLATFORM_LIBS)
1516
=== modified file 'Thunderbird/Thunderbird.mdp'
--- Thunderbird/Thunderbird.mdp 2009-06-23 00:52:54 +0000
+++ Thunderbird/Thunderbird.mdp 2011-02-08 03:44:13 +0000
@@ -22,5 +22,6 @@
22 <References>22 <References>
23 <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />23 <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
24 <ProjectReference type="Gac" localcopy="True" refto="Do.Universe, Version=0.9.0.0, Culture=neutral" />24 <ProjectReference type="Gac" localcopy="True" refto="Do.Universe, Version=0.9.0.0, Culture=neutral" />
25 <ProjectReference type="Gac" localcopy="True" refto="Do.Platform, Version=0.9.0.0, Culture=neutral" />
25 </References>26 </References>
26</Project>27</Project>
27\ No newline at end of file28\ No newline at end of file
2829
=== modified file 'Thunderbird/src/ThunderbirdContactItemSource.cs'
--- Thunderbird/src/ThunderbirdContactItemSource.cs 2009-05-29 09:39:41 +0000
+++ Thunderbird/src/ThunderbirdContactItemSource.cs 2011-02-08 03:44:13 +0000
@@ -17,14 +17,13 @@
17// You should have received a copy of the GNU General Public License17// You should have received a copy of the GNU General Public License
18// along with this program. If not, see <http://www.gnu.org/licenses/>.18// along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
20//
21
22using System;20using System;
23using System.IO;21using System.IO;
24using System.Collections;22using System.Collections;
25using System.Collections.Generic;23using System.Collections.Generic;
2624
27using Do.Universe;25using Do.Universe;
26using Do.Platform;
2827
29using Beagle.Util;28using Beagle.Util;
3029
@@ -33,16 +32,107 @@
3332
34 public class ThunderbirdContactItemSource : ItemSource33 public class ThunderbirdContactItemSource : ItemSource
35 {34 {
36 35
37 const string BeginProfileName = "Path=";36 class EmailContactDetail: Item, IContactDetailItem
37 {
38 readonly string detail, description;
39 readonly ContactItem owner;
40
41 public EmailContactDetail (ContactItem owner, string detail)
42 {
43 this.owner = owner;
44 this.detail = detail;
45 description = string.IsNullOrEmpty (owner["name"])
46 ? owner[detail]
47 : owner["name"];
48 }
49
50 public override string Name
51 {
52 get { return owner[detail]; }
53 }
54
55 public override string Description
56 {
57 get { return description; }
58 }
59
60 public override string Icon
61 {
62 get { return "thunderbird"; }
63 }
64
65 public string Key
66 {
67 get { return detail; }
68 }
69
70 public string Value
71 {
72 get { return owner[detail]; }
73 }
74 }
75
76 class EmailList
77 {
78 private Dictionary<string, uint> set;
79
80 public EmailList () { set = new Dictionary<string, uint> (); }
81
82 public void Add (string email, uint popularity)
83 {
84 if (!set.ContainsKey (email))
85 {
86 set.Add (email, popularity);
87 }
88 else
89 {
90 set[email] += popularity;
91 }
92 }
93
94 public bool Contains (string email)
95 {
96 return set.ContainsKey (email);
97 }
98
99 public uint this [string email]
100 {
101 get { return set[email]; }
102 }
103
104 public int Count {
105 get { return set.Count; }
106 }
107
108 public ICollection<string> Keys {
109 get { return set.Keys; }
110 }
111 }
112
113 class ThunderbirdEmail
114 {
115 public readonly string email;
116 public readonly uint popularity;
117
118 public ThunderbirdEmail (string email, uint popularity)
119 {
120 this.email = email;
121 this.popularity = popularity;
122 }
123 }
124
125
126 const string BeginProfileName = "Path=";
38 const string BeginDefaultProfile = "Name=default";127 const string BeginDefaultProfile = "Name=default";
39 128 const string THUNDERBIRD_EMAIL = "email.thunderbird";
40 List<Item> contacts;129 static readonly char[] nameDelimiters = { ' ', '\'', '"' };
130
131 Dictionary<string, Item> contacts; // name => ContactItem
41 132
42 public ThunderbirdContactItemSource ()133 public ThunderbirdContactItemSource ()
43 {134 {
44 contacts = new List<Item> ();135 contacts = new Dictionary<string, Item> ();
45 UpdateItems ();
46 }136 }
47 137
48 public override IEnumerable<Type> SupportedItemTypes {138 public override IEnumerable<Type> SupportedItemTypes {
@@ -50,7 +140,7 @@
50 return new Type[] {140 return new Type[] {
51 typeof (ContactItem),141 typeof (ContactItem),
52 };142 };
53 }143 }
54 }144 }
55 145
56 public override string Name { get { return "Thunderbird Contacts"; } }146 public override string Name { get { return "Thunderbird Contacts"; } }
@@ -62,73 +152,135 @@
62 try {152 try {
63 _UpdateItems ();153 _UpdateItems ();
64 } catch (Exception e) {154 } catch (Exception e) {
65 Console.Error.WriteLine ("Cannot index Thunderbird contacts because a {0} was thrown: {1}", e.GetType (), e.Message);155 Log<ThunderbirdContactItemSource>.Error ("Cannot index Thunderbird contacts because a {0} was thrown: {1}", e.GetType (), e.Message);
66 return;156 return;
67 }157 }
68 }158 }
69 159
70 public override IEnumerable<Item> Items {160 public override IEnumerable<Item> Items {
71 get { return contacts; }161 get { return contacts.Values; }
72 }162 }
73 163
74 public override IEnumerable<Item> ChildrenOfItem (Item item)164 public override IEnumerable<Item> ChildrenOfItem (Item item)
75 {165 {
166 ContactItem contact = item as ContactItem;
167
168 foreach (string detail in contact.Details)
169 {
170 if (detail.StartsWith (THUNDERBIRD_EMAIL))
171 {
172 yield return new EmailContactDetail (contact, detail);
173 }
174 }
76 yield break;175 yield break;
77 }176 }
78 177
79 void _UpdateItems ()178 void _UpdateItems ()
80 {179 {
81 MorkDatabase database;180 MorkDatabase abook, history;
82 181 Dictionary<string, EmailList> emails = new Dictionary<string, EmailList> ();
182
183 abook = new MorkDatabase (GetThunderbirdAddressBookFilePath ());
184 abook.Read ();
185 abook.EnumNamespace = "ns:addrbk:db:row:scope:card:all";
186
187 history = new MorkDatabase (GetThunderbirdHistoryFilePath ());
188 history.Read ();
189 history.EnumNamespace = "ns:addrbk:db:row:scope:card:all";
190
191 addEmails (emails, history);
192 addEmails (emails, abook);
193
83 contacts.Clear ();194 contacts.Clear ();
84 database = new MorkDatabase (GetThunderbirdAddressBookFilePath ());195 foreach (string name in emails.Keys)
85 database.Read ();196 {
86 database.EnumNamespace = "ns:addrbk:db:row:scope:card:all";197 CreateThunderbirdContactItem (name, emails[name]);
198 }
199 }
87200
88 foreach (string id in database) {201 void addEmails (Dictionary<string, EmailList> emails, MorkDatabase database)
89 Hashtable contact_row;202 {
90 ContactItem contact;203 foreach (string id in database)
91 204 {
92 contact_row = database.Compile (id, database.EnumNamespace);205 Hashtable contact_row = database.Compile (id, database.EnumNamespace);
93 contact = CreateThunderbirdContactItem (contact_row);206 AddThunderbirdEmail (emails, contact_row);
94 if (contact != null)
95 contacts.Add (contact);
96 }207 }
97 }208 }
98 209
99 ContactItem CreateThunderbirdContactItem (Hashtable row) {210 void AddThunderbirdEmail (Dictionary<string, EmailList> emails, Hashtable row)
100 ContactItem contact;211 {
101 string name, email;212 string name, email;
102 213 uint popularity;
103// foreach (object o in row.Keys)
104// Console.WriteLine ("\t{0} --> {1}", o, row[o]);
105 214
106 // I think this will detect deleted contacts... Hmm...215 // I think this will detect deleted contacts... Hmm...
107 if (row["table"] == null || row["table"] as string == "C6")216 if (row["table"] == null || row["table"] as string == "C6")
108 return null;217 return;
109 218
110 // Name219 // Name
111 name = row["DisplayName"] as string;220 name = row["DisplayName"] as string;
221 if (name != null) {
222 name = name.Trim (nameDelimiters);
223 }
112 if (name == null || name == string.Empty)224 if (name == null || name == string.Empty)
113 name = string.Format ("{0} {1}", row["FirstName"], row["LastName"]);225 name = string.Format ("{0} {1}", row["FirstName"], row["LastName"]);
114 contact = ContactItem.Create (name);
115 226
116 // Email227 // Email
117 email = row["PrimaryEmail"] as string;228 email = row["PrimaryEmail"] as string;
118 if (email != null && email != string.Empty)229 string p = row["PopularityIndex"] as string;
119 contact["email"] = email;230 try {
231 popularity = UInt32.Parse (p, System.Globalization.NumberStyles.HexNumber);
232 }
233 catch (Exception) {
234 popularity = 0;
235 }
120 236
121 return contact;237 if (name == null || name.Trim () == string.Empty)
122 }238 name = email;
123 239
124 string GetThunderbirdAddressBookFilePath ()240 if (string.IsNullOrEmpty (email))
241 return;
242
243 if (!emails.ContainsKey (name))
244 {
245 emails[name] = new EmailList ();
246 }
247 emails[name].Add (email, popularity);
248 }
249
250 void CreateThunderbirdContactItem (string name, EmailList emails)
251 {
252 int emailCount = emails.Count;
253 ThunderbirdEmail[] sortedEmails = new ThunderbirdEmail[emailCount];
254
255 int i = 0;
256 foreach (string key in emails.Keys)
257 {
258 sortedEmails[i] = new ThunderbirdEmail (key, emails[key]);
259 i++;
260 }
261 Array.Sort (sortedEmails, (x, y) => (int) (y.popularity - x.popularity));
262
263 ContactItem contact = ContactItem.Create (name);
264 for (i = 0; i < emailCount; i++)
265 {
266 string detail = THUNDERBIRD_EMAIL + "." + i;
267 contact[detail] = sortedEmails[i].email;
268 }
269
270 if (!contacts.ContainsKey (name.ToLower ()))
271 {
272 contacts.Add (name.ToLower (), contact);
273 }
274 }
275
276 string GetThunderbirdDefaultProfilePath ()
125 {277 {
126 string home, path, profile;278 string home, path, profile;
127 StreamReader reader;279 StreamReader reader;
128280
129 profile = null;281 profile = null;
130 home = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal);282 home = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal);
131 path = System.IO.Path.Combine (home, ".mozilla-thunderbird/profiles.ini");283 path = System.IO.Path.Combine (home, ".thunderbird/profiles.ini");
132 try {284 try {
133 reader = System.IO.File.OpenText (path);285 reader = System.IO.File.OpenText (path);
134 } catch {286 } catch {
@@ -148,16 +300,31 @@
148 }300 }
149 }301 }
150 reader.Close ();302 reader.Close ();
151 303 return profile;
304 }
305
306 string GetThunderbirdFilePath (string filename)
307 {
308 string path, home, profile;
309 home = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal);
310 profile = GetThunderbirdDefaultProfilePath ();
152 if (profile == null) {311 if (profile == null) {
153 return null;312 return null;
154 }313 }
155 path = System.IO.Path.Combine (home, ".mozilla-thunderbird");314 path = System.IO.Path.Combine (home, ".thunderbird");
156 path = System.IO.Path.Combine (path, profile);315 path = System.IO.Path.Combine (path, profile);
157 path = System.IO.Path.Combine (path, "abook.mab");316 path = System.IO.Path.Combine (path, filename);
158 return path;317 return path;
159
160 }318 }
161 319
320 string GetThunderbirdHistoryFilePath ()
321 {
322 return GetThunderbirdFilePath ("history.mab");
323 }
324
325 string GetThunderbirdAddressBookFilePath ()
326 {
327 return GetThunderbirdFilePath ("abook.mab");
328 }
162 }329 }
163}330}

Subscribers

People subscribed via source and target branches