Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(373)

Side by Side Diff: chrome/browser/sync/notifier/communicator/mailbox.cc

Issue 1956001: Moved XMPP notifier library from chrome/browser/sync to chrome/common.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/sync/notifier/communicator/mailbox.h"
6
7 #include <assert.h>
8 #include <stdlib.h>
9
10 #include <stack>
11 #include <vector>
12
13 #include "chrome/browser/sync/notifier/base/string.h"
14 #include "chrome/browser/sync/notifier/base/utils.h"
15 #include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h"
16 #include "talk/base/basictypes.h"
17 #include "talk/base/common.h"
18 #include "talk/base/stringutils.h"
19 #include "talk/xmllite/xmlelement.h"
20 #include "talk/xmpp/constants.h"
21
22 namespace notifier {
23
24 // Labels are a list of strings seperated by a '|' character. The '|' character
25 // is escaped with a backslash ('\\') and the backslash is also escaped with a
26 // backslash.
27 static void ParseLabelSet(const std::string& text,
28 MessageThread::StringSet* labels) {
29 const char* input_cur = text.c_str();
30 const char* input_end = input_cur + text.size();
31 char* result = new char[text.size() + 1];
32 char* next_write = result;
33
34 while (input_cur < input_end) {
35 if (*input_cur == '|') {
36 if (next_write != result) {
37 *next_write = '\0';
38 labels->insert(std::string(result));
39 next_write = result;
40 }
41 input_cur++;
42 continue;
43 }
44
45 if (*input_cur == '\\') {
46 // Skip a character in the input and break if we are at the end.
47 input_cur++;
48 if (input_cur >= input_end)
49 break;
50 }
51 *next_write = *input_cur;
52 next_write++;
53 input_cur++;
54 }
55
56 if (next_write != result) {
57 *next_write = '\0';
58 labels->insert(std::string(result));
59 }
60
61 delete [] result;
62 }
63
64 // -----------------------------------------------------------------------------
65
66 std::string MailAddress::safe_name() const {
67 if (!name().empty()) {
68 return name();
69 }
70
71 if (!address().empty()) {
72 size_t at = address().find('@');
73 if (at == std::string::npos) {
74 return address();
75 }
76
77 if (at != 0) {
78 return address().substr(0, at);
79 }
80 }
81
82 return std::string("(unknown)");
83 }
84
85 // -----------------------------------------------------------------------------
86 MessageThread::~MessageThread() {
87 Clear();
88 }
89
90 void MessageThread::Clear() {
91 delete labels_;
92 labels_ = NULL;
93
94 delete senders_;
95 senders_ = NULL;
96 }
97
98 MessageThread& MessageThread::operator=(const MessageThread& r) {
99 if (&r != this) {
100 Clear();
101 // Copy everything.
102 r.AssertValid();
103 thread_id_ = r.thread_id_;
104 date64_ = r.date64_;
105 message_count_ = r.message_count_;
106 personal_level_ = r.personal_level_;
107 subject_ = r.subject_;
108 snippet_ = r.snippet_;
109
110 if (r.labels_)
111 labels_ = new StringSet(*r.labels_);
112 else
113 labels_ = new StringSet;
114 if (r.senders_)
115 senders_ = new MailSenderList(*r.senders_);
116 else
117 senders_ = new MailSenderList;
118 }
119 AssertValid();
120 return *this;
121 }
122
123 MessageThread* MessageThread::CreateFromXML(
124 const buzz::XmlElement* src) {
125 MessageThread* info = new MessageThread();
126 if (!info || !info->InitFromXml(src)) {
127 delete info;
128 return NULL;
129 }
130 return info;
131 }
132
133 // Init from a chunk of XML.
134 bool MessageThread::InitFromXml(const buzz::XmlElement* src) {
135 labels_ = new StringSet;
136 senders_ = new MailSenderList;
137
138 if (src->Name() != buzz::kQnMailThreadInfo)
139 return false;
140
141 if (!ParseInt64Attr(src, buzz::kQnMailTid, &thread_id_))
142 return false;
143 if (!ParseInt64Attr(src, buzz::kQnMailDate, &date64_))
144 return false;
145 if (!ParseIntAttr(src, buzz::kQnMailMessages, &message_count_))
146 return false;
147 if (!ParseIntAttr(src, buzz::kQnMailParticipation, &personal_level_))
148 return false;
149
150 const buzz::XmlElement* senders = src->FirstNamed(buzz::kQnMailSenders);
151 if (!senders)
152 return false;
153 for (const buzz::XmlElement* child = senders->FirstElement();
154 child != NULL;
155 child = child->NextElement()) {
156 if (child->Name() != buzz::kQnMailSender)
157 continue;
158 std::string address;
159 if (!ParseStringAttr(child, buzz::kQnMailAddress, &address))
160 continue;
161 std::string name;
162 ParseStringAttr(child, buzz::kQnMailName, &name);
163 bool originator = false;
164 ParseBoolAttr(child, buzz::kQnMailOriginator, &originator);
165 bool unread = false;
166 ParseBoolAttr(child, buzz::kQnMailUnread, &unread);
167
168 senders_->push_back(MailSender(name, address, unread, originator));
169 }
170
171 const buzz::XmlElement* labels = src->FirstNamed(buzz::kQnMailLabels);
172 if (!labels)
173 return false;
174 ParseLabelSet(labels->BodyText(), labels_);
175
176 const buzz::XmlElement* subject = src->FirstNamed(buzz::kQnMailSubject);
177 if (!subject)
178 return false;
179 subject_ = subject->BodyText();
180
181 const buzz::XmlElement* snippet = src->FirstNamed(buzz::kQnMailSnippet);
182 if (!snippet)
183 return false;
184 snippet_ = snippet->BodyText();
185
186 AssertValid();
187 return true;
188 }
189
190 bool MessageThread::starred() const {
191 return (labels_->find("^t") != labels_->end());
192 }
193
194 bool MessageThread::unread() const {
195 return (labels_->find("^u") != labels_->end());
196 }
197
198 #if defined(DEBUG)
199 // Non-debug version is inline and empty.
200 void MessageThread::AssertValid() const {
201 assert(thread_id_ != 0);
202 assert(senders_ != NULL);
203 // In some (odd) cases, gmail may send email with no sender.
204 // assert(!senders_->empty());
205 assert(message_count_ > 0);
206 assert(labels_ != NULL);
207 }
208 #endif
209
210 MailBox* MailBox::CreateFromXML(const buzz::XmlElement* src) {
211 MailBox* mail_box = new MailBox();
212 if (!mail_box || !mail_box->InitFromXml(src)) {
213 delete mail_box;
214 return NULL;
215 }
216 return mail_box;
217 }
218
219 // Init from a chunk of XML.
220 bool MailBox::InitFromXml(const buzz::XmlElement* src) {
221 if (src->Name() != buzz::kQnMailBox)
222 return false;
223
224 if (!ParseIntAttr(src, buzz::kQnMailTotalMatched, &mailbox_size_))
225 return false;
226
227 estimate_ = false;
228 ParseBoolAttr(src, buzz::kQnMailTotalEstimate, &estimate_);
229
230 first_index_ = 0;
231 ParseIntAttr(src, buzz::kQnMailFirstIndex, &first_index_);
232
233 result_time_ = 0;
234 ParseInt64Attr(src, buzz::kQnMailResultTime, &result_time_);
235
236 highest_thread_id_ = 0;
237
238 const buzz::XmlElement* thread_element = src->FirstNamed(buzz::kQnMailThreadIn fo);
239 while (thread_element) {
240 MessageThread* thread = MessageThread::CreateFromXML(thread_element);
241 if (thread) {
242 if (thread->thread_id() > highest_thread_id_)
243 highest_thread_id_ = thread->thread_id();
244 threads_.push_back(MessageThreadPointer(thread));
245 }
246 thread_element = thread_element->NextNamed(buzz::kQnMailThreadInfo);
247 }
248 return true;
249 }
250
251 const size_t kMaxShortnameLength = 12;
252
253 // Tip: If you extend this list of chars, do not include '-'.
254 const char name_delim[] = " ,.:;\'\"()[]{}<>*@";
255
256 class SenderFormatter {
257 public:
258 // sender should not be deleted while this class is in use.
259 SenderFormatter(const MailSender& sender,
260 const std::string& me_address)
261 : sender_(sender),
262 visible_(false),
263 short_format_(true),
264 space_(kMaxShortnameLength) {
265 me_ = talk_base::ascicmp(me_address.c_str(),
266 sender.address().c_str()) == 0;
267 }
268
269 bool visible() const {
270 return visible_;
271 }
272
273 bool is_unread() const {
274 return sender_.unread();
275 }
276
277 const std::string name() const {
278 return name_;
279 }
280
281 void set_short_format(bool short_format) {
282 short_format_ = short_format;
283 UpdateName();
284 }
285
286 void set_visible(bool visible) {
287 visible_ = visible;
288 UpdateName();
289 }
290
291 void set_space(size_t space) {
292 space_ = space;
293 UpdateName();
294 }
295
296 private:
297 // Attempt to shorten to the first word in a person's name We could revisit
298 // and do better at international punctuation, but this is what cricket did,
299 // and it should be removed soon when gmail does the notification instead of
300 // us forming it on the client.
301 static void ShortenName(std::string* name) {
302 size_t start = name->find_first_not_of(name_delim);
303 if (start != std::string::npos && start > 0) {
304 name->erase(0, start);
305 }
306 start = name->find_first_of(name_delim);
307 if (start != std::string::npos) {
308 name->erase(start);
309 }
310 }
311
312 void UpdateName() {
313 // Update the name if is going to be used.
314 if (!visible_) {
315 return;
316 }
317
318 if (me_) {
319 name_ = "me";
320 return;
321 }
322
323 if (sender_.name().empty() && sender_.address().empty()) {
324 name_ = "";
325 return;
326 }
327
328 name_ = sender_.name();
329 // Handle the case of no name or a name looks like an email address. When
330 // mail is sent to "Quality@example.com" <quality-team@example.com>, we
331 // shouldn't show "Quality@example.com" as the name. Instead, use the email
332 // address (without the @...)
333 if (name_.empty() || name_.find_first_of("@") != std::string::npos) {
334 name_ = sender_.address();
335 size_t at_index = name_.find_first_of("@");
336 if (at_index != std::string::npos) {
337 name_.erase(at_index);
338 }
339 } else if (short_format_) {
340 ShortenName(&name_);
341 }
342
343 if (name_.empty()) {
344 name_ = "(unknown)";
345 }
346
347 // Abbreviate if too long.
348 if (name_.size() > space_) {
349 name_.replace(space_ - 1, std::string::npos, ".");
350 }
351 }
352
353 const MailSender& sender_;
354 std::string name_;
355 bool visible_;
356 bool short_format_;
357 size_t space_;
358 bool me_;
359 DISALLOW_COPY_AND_ASSIGN(SenderFormatter);
360 };
361
362 const char kNormalSeparator[] = ",&nbsp;";
363 const char kEllidedSeparator[] = "&nbsp;..";
364
365 std::string FormatName(const std::string& name, bool bold) {
366 std::string formatted_name;
367 if (bold) {
368 formatted_name.append("<b>");
369 }
370 formatted_name.append(HtmlEncode(name));
371 if (bold) {
372 formatted_name.append("</b>");
373 }
374 return formatted_name;
375 }
376
377 class SenderFormatterList {
378 public:
379 // sender_list must not be deleted while this class is being used.
380 SenderFormatterList(const MailSenderList& sender_list,
381 const std::string& me_address)
382 : state_(INITIAL_STATE),
383 are_any_read_(false),
384 index_(-1),
385 first_unread_index_(-1) {
386 // Add all read messages.
387 const MailSender* originator = NULL;
388 bool any_unread = false;
389 for (size_t i = 0; i < sender_list.size(); ++i) {
390 if (sender_list[i].originator()) {
391 originator = &sender_list[i];
392 }
393 if (sender_list[i].unread()) {
394 any_unread = true;
395 continue;
396 }
397 are_any_read_ = true;
398 if (!sender_list[i].originator()) {
399 senders_.push_back(new SenderFormatter(sender_list[i],
400 me_address));
401 }
402 }
403
404 // There may not be an orignator (if there no senders).
405 if (originator) {
406 senders_.insert(senders_.begin(), new SenderFormatter(*originator,
407 me_address));
408 }
409
410 // Add all unread messages.
411 if (any_unread) {
412 // It should be rare, but there may be cases when all of the senders
413 // appear to have read the message.
414 first_unread_index_ = is_first_unread() ? 0 : senders_.size();
415 for (size_t i = 0; i < sender_list.size(); ++i) {
416 if (!sender_list[i].unread()) {
417 continue;
418 }
419 // Don't add the originator if it is already at the start of the
420 // "unread" list.
421 if (sender_list[i].originator() && is_first_unread()) {
422 continue;
423 }
424 senders_.push_back(new SenderFormatter(sender_list[i], me_address));
425 }
426 }
427 }
428
429 ~SenderFormatterList() {
430 CleanupSequence(&senders_);
431 }
432
433 std::string GetHtml(int space) {
434 index_ = -1;
435 state_ = INITIAL_STATE;
436 while (!added_.empty()) {
437 added_.pop();
438 }
439
440 // If there is only one sender, let it take up all of the space.
441 if (senders_.size() == 1) {
442 senders_[0]->set_space(space);
443 senders_[0]->set_short_format(false);
444 }
445
446 int length = 1;
447 // Add as many senders as we can in the given space. Computes the visible
448 // length at each iteration, but does not construct the actual html.
449 while (length < space && AddNextSender()) {
450 int new_length = ConstructHtml(is_first_unread(), NULL);
451 // Remove names to avoid truncating
452 // * if there will be at least 2 left or
453 // * if the spacing <= 2 characters per sender and there
454 // is at least one left.
455 if ((new_length > space &&
456 (visible_count() > 2 ||
457 (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) {
458 RemoveLastAddedSender();
459 break;
460 }
461 length = new_length;
462 }
463
464 if (length > space) {
465 int max = ComputeSpacePerSender(space);
466 for (size_t i = 0; i < senders_.size(); ++i) {
467 if (senders_[i]->visible()) {
468 senders_[i]->set_space(max);
469 }
470 }
471 }
472
473 // Now construct the actual html.
474 std::string html_list;
475 length = ConstructHtml(is_first_unread(), &html_list);
476 if (length > space) {
477 LOG(LS_WARNING) << "LENGTH: " << length << " exceeds "
478 << space << " " << html_list;
479 }
480 return html_list;
481 }
482
483 private:
484 int ComputeSpacePerSender(int space) const {
485 // Why the "- 2"? To allow for the " .. " which may occur after the names,
486 // and no matter what always allow at least 2 characters per sender.
487 return talk_base::_max<int>(space / visible_count() - 2, 2);
488 }
489
490 // Finds the next sender that should be added to the "from" list and sets it
491 // to visible.
492 //
493 // This method may be called until it returns false or until
494 // RemoveLastAddedSender is called.
495 bool AddNextSender() {
496 // The progression is:
497 // 1. Add the person who started the thread, which is the first message.
498 // 2. Add the first unread message (unless it has already been added).
499 // 3. Add the last message (unless it has already been added).
500 // 4. Add the message that is just before the last message processed
501 // (unless it has already been added).
502 // If there is no message (i.e. at index -1), return false.
503 //
504 // Typically, this method is called until it returns false or all of the
505 // space available is used.
506 switch (state_) {
507 case INITIAL_STATE:
508 state_ = FIRST_MESSAGE;
509 index_ = 0;
510 // If the server behaves odd and doesn't send us any senders, do
511 // something graceful.
512 if (senders_.size() == 0) {
513 return false;
514 }
515 break;
516
517 case FIRST_MESSAGE:
518 if (first_unread_index_ >= 0) {
519 state_ = FIRST_UNREAD_MESSAGE;
520 index_ = first_unread_index_;
521 break;
522 }
523 // Fall through.
524 case FIRST_UNREAD_MESSAGE:
525 state_ = LAST_MESSAGE;
526 index_ = senders_.size() - 1;
527 break;
528
529 case LAST_MESSAGE:
530 case PREVIOUS_MESSAGE:
531 state_ = PREVIOUS_MESSAGE;
532 index_--;
533 break;
534
535 case REMOVED_MESSAGE:
536 default:
537 ASSERT(false);
538 return false;
539 }
540
541 if (index_ < 0) {
542 return false;
543 }
544
545 if (!senders_[index_]->visible()) {
546 added_.push(index_);
547 senders_[index_]->set_visible(true);
548 }
549 return true;
550 }
551
552 // Makes the last added sender not visible.
553 //
554 // May be called while visible_count() > 0.
555 void RemoveLastAddedSender() {
556 state_ = REMOVED_MESSAGE;
557 int index = added_.top();
558 added_.pop();
559 senders_[index]->set_visible(false);
560 }
561
562 // Constructs the html of the SenderList and returns the length of the
563 // visible text.
564 //
565 // The algorithm simply walks down the list of Senders, appending the html
566 // for each visible sender, and adding ellipsis or commas in between,
567 // whichever is appropriate.
568 //
569 // html Filled with html. Maybe NULL if the html doesn't need to be
570 // constructed yet (useful for simply determining the length of the
571 // visible text).
572 //
573 // returns The approximate visible length of the html.
574 int ConstructHtml(bool first_is_unread,
575 std::string* html) const {
576 if (senders_.empty()) {
577 return 0;
578 }
579
580 int length = 0;
581
582 // The first is always visible.
583 const SenderFormatter* sender = senders_[0];
584 const std::string& originator_name = sender->name();
585 length += originator_name.length();
586 if (html) {
587 html->append(FormatName(originator_name, first_is_unread));
588 }
589
590 bool elided = false;
591 const char* between = "";
592 for (size_t i = 1; i < senders_.size(); i++) {
593 sender = senders_[i];
594
595 if (sender->visible()) {
596 // Handle the separator.
597 between = elided ? "&nbsp;" : kNormalSeparator;
598 // Ignore the , for length because it is so narrow, so in both cases
599 // above the space is the only things that counts for spaces.
600 length++;
601
602 // Handle the name.
603 const std::string name = sender->name();
604 length += name.size();
605
606 // Construct the html.
607 if (html) {
608 html->append(between);
609 html->append(FormatName(name, sender->is_unread()));
610 }
611 elided = false;
612 } else if (!elided) {
613 between = kEllidedSeparator;
614 length += 2; // ".." is narrow.
615 if (html) {
616 html->append(between);
617 }
618 elided = true;
619 }
620 }
621 return length;
622 }
623
624 bool is_first_unread() const {
625 return !are_any_read_;
626 }
627
628 size_t visible_count() const {
629 return added_.size();
630 }
631
632 enum MessageState {
633 INITIAL_STATE,
634 FIRST_MESSAGE,
635 FIRST_UNREAD_MESSAGE,
636 LAST_MESSAGE,
637 PREVIOUS_MESSAGE,
638 REMOVED_MESSAGE,
639 };
640
641 // What state we were in during the last "stateful" function call.
642 MessageState state_;
643 bool are_any_read_;
644 std::vector<SenderFormatter*> senders_;
645 std::stack<int> added_;
646 int index_;
647 int first_unread_index_;
648 DISALLOW_COPY_AND_ASSIGN(SenderFormatterList);
649 };
650
651
652 std::string GetSenderHtml(const MailSenderList& sender_list,
653 int message_count,
654 const std::string& me_address,
655 int space) {
656 // There has to be at least 9 spaces to show something reasonable.
657 ASSERT(space >= 10);
658 std::string count_html;
659 if (message_count > 1) {
660 std::string count(IntToString(message_count));
661 space -= (count.size());
662 count_html.append("&nbsp;(");
663 count_html.append(count);
664 count_html.append(")");
665 // Reduce space due to parenthesis and &nbsp;.
666 space -= 2;
667 }
668
669 SenderFormatterList senders(sender_list, me_address);
670 std::string html_list(senders.GetHtml(space));
671 html_list.append(count_html);
672 return html_list;
673 }
674
675 } // namespace notifier
OLDNEW
« no previous file with comments | « chrome/browser/sync/notifier/communicator/mailbox.h ('k') | chrome/browser/sync/notifier/communicator/mailbox_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698