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

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

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 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
Property Changes:
Added: svn:eol-style
+ LF
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.
25 // The '|' character is escaped with a backslash ('\\') and the
26 // backslash is also escaped with a 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 #ifdef _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
211
212 MailBox* MailBox::CreateFromXML(const buzz::XmlElement* src) {
213 MailBox* mail_box = new MailBox();
214 if (!mail_box || !mail_box->InitFromXml(src)) {
215 delete mail_box;
216 return NULL;
217 }
218 return mail_box;
219 }
220
221 // Init from a chunk of XML
222 bool MailBox::InitFromXml(const buzz::XmlElement* src)
223 {
224 if (src->Name() != buzz::kQnMailBox)
225 return false;
226
227 if (!ParseIntAttr(src, buzz::kQnMailTotalMatched, &mailbox_size_))
228 return false;
229
230 estimate_ = false;
231 ParseBoolAttr(src, buzz::kQnMailTotalEstimate, &estimate_);
232
233 first_index_ = 0;
234 ParseIntAttr(src, buzz::kQnMailFirstIndex, &first_index_);
235
236 result_time_ = 0;
237 ParseInt64Attr(src, buzz::kQnMailResultTime, &result_time_);
238
239 highest_thread_id_ = 0;
240
241 const buzz::XmlElement* thread_element = src->FirstNamed(buzz::kQnMailThreadIn fo);
242 while (thread_element) {
243 MessageThread* thread = MessageThread::CreateFromXML(thread_element);
244 if (thread) {
245 if (thread->thread_id() > highest_thread_id_)
246 highest_thread_id_ = thread->thread_id();
247 threads_.push_back(MessageThreadPointer(thread));
248 }
249 thread_element = thread_element->NextNamed(buzz::kQnMailThreadInfo);
250 }
251 return true;
252 }
253
254 const size_t kMaxShortnameLength = 12;
255
256 // Tip: If you extend this list of chars, do not include '-'
257 const char name_delim[] = " ,.:;\'\"()[]{}<>*@";
258
259 class SenderFormatter {
260 public:
261 // sender should not be deleted while this class is in use.
262 SenderFormatter(const MailSender& sender,
263 const std::string& me_address)
264 : sender_(sender),
265 visible_(false),
266 short_format_(true),
267 space_(kMaxShortnameLength) {
268 me_ = talk_base::ascicmp(me_address.c_str(),
269 sender.address().c_str()) == 0;
270 }
271
272 bool visible() const {
273 return visible_;
274 }
275
276 bool is_unread() const {
277 return sender_.unread();
278 }
279
280 const std::string name() const {
281 return name_;
282 }
283
284 void set_short_format(bool short_format) {
285 short_format_ = short_format;
286 UpdateName();
287 }
288
289 void set_visible(bool visible) {
290 visible_ = visible;
291 UpdateName();
292 }
293
294 void set_space(size_t space) {
295 space_ = space;
296 UpdateName();
297 }
298
299 private:
300 // Attempt to shorten to the first word in a person's name
301 // We could revisit and do better at international punctuation,
302 // but this is what cricket did, and it should be removed
303 // soon when gmail does the notification instead of us
304 // forming it on the client.
305 static void ShortenName(std::string* name) {
306 size_t start = name->find_first_not_of(name_delim);
307 if (start != std::string::npos && start > 0) {
308 name->erase(0, start);
309 }
310 start = name->find_first_of(name_delim);
311 if (start != std::string::npos) {
312 name->erase(start);
313 }
314 }
315
316 void UpdateName() {
317 // Update the name if is going to be used.
318 if (!visible_) {
319 return;
320 }
321
322 if (me_) {
323 name_ = "me";
324 return;
325 }
326
327 if (sender_.name().empty() && sender_.address().empty()) {
328 name_ = "";
329 return;
330 }
331
332 name_ = sender_.name();
333 // Handle the case of no name or a name looks like an email address.
334 // When mail is sent to "Quality@example.com" <quality-team@example.com>,
335 // we shouldn't show "Quality@example.com" as the name.
336 // Instead use the email address (without the @...)
337 if (name_.empty() || name_.find_first_of("@") != std::string::npos) {
338 name_ = sender_.address();
339 size_t at_index = name_.find_first_of("@");
340 if (at_index != std::string::npos) {
341 name_.erase(at_index);
342 }
343 } else if (short_format_) {
344 ShortenName(&name_);
345 }
346
347 if (name_.empty()) {
348 name_ = "(unknown)";
349 }
350
351 // Abbreviate if too long.
352 if (name_.size() > space_) {
353 name_.replace(space_ - 1, std::string::npos, ".");
354 }
355 }
356
357 const MailSender& sender_;
358 std::string name_;
359 bool visible_;
360 bool short_format_;
361 size_t space_;
362 bool me_;
363 DISALLOW_COPY_AND_ASSIGN(SenderFormatter);
364 };
365
366 const char kNormalSeparator[] = ",&nbsp;";
367 const char kEllidedSeparator[] = "&nbsp;..";
368
369 std::string FormatName(const std::string& name, bool bold) {
370 std::string formatted_name;
371 if (bold) {
372 formatted_name.append("<b>");
373 }
374 formatted_name.append(HtmlEncode(name));
375 if (bold) {
376 formatted_name.append("</b>");
377 }
378 return formatted_name;
379 }
380
381 class SenderFormatterList {
382 public:
383 // sender_list must not be deleted while this class is being used.
384 SenderFormatterList(const MailSenderList& sender_list,
385 const std::string& me_address)
386 : state_(INITIAL_STATE),
387 are_any_read_(false),
388 index_(-1),
389 first_unread_index_(-1) {
390 // Add all read messages.
391 const MailSender* originator = NULL;
392 bool any_unread = false;
393 for (size_t i = 0; i < sender_list.size(); ++i) {
394 if (sender_list[i].originator()) {
395 originator = &sender_list[i];
396 }
397 if (sender_list[i].unread()) {
398 any_unread = true;
399 continue;
400 }
401 are_any_read_ = true;
402 if (!sender_list[i].originator()) {
403 senders_.push_back(new SenderFormatter(sender_list[i],
404 me_address));
405 }
406 }
407
408 // There may not be an orignator (if there no senders).
409 if (originator) {
410 senders_.insert(senders_.begin(), new SenderFormatter(*originator,
411 me_address));
412 }
413
414 // Add all unread messages.
415 if (any_unread) {
416 // It should be rare, but there may be cases when all of the
417 // senders appear to have read the message.
418 first_unread_index_ = is_first_unread() ? 0 : senders_.size();
419 for (size_t i = 0; i < sender_list.size(); ++i) {
420 if (!sender_list[i].unread()) {
421 continue;
422 }
423 // Don't add the originator if it is already at the
424 // start of the "unread" list.
425 if (sender_list[i].originator() && is_first_unread()) {
426 continue;
427 }
428 senders_.push_back(new SenderFormatter(sender_list[i],
429 me_address));
430 }
431 }
432 }
433
434 ~SenderFormatterList() {
435 CleanupSequence(&senders_);
436 }
437
438 std::string GetHtml(int space) {
439 index_ = -1;
440 state_ = INITIAL_STATE;
441 while (!added_.empty()) {
442 added_.pop();
443 }
444
445 // If there is only one sender, let it take up all of the space.
446 if (senders_.size() == 1) {
447 senders_[0]->set_space(space);
448 senders_[0]->set_short_format(false);
449 }
450
451 int length = 1;
452 // Add as many senders as we can in the given space.
453 // Computes the visible length at each iteration,
454 // but does not construct the actual html.
455 while (length < space && AddNextSender()) {
456 int new_length = ConstructHtml(is_first_unread(), NULL);
457 // Remove names to avoid truncating
458 // * if there will be at least 2 left or
459 // * if the spacing <= 2 characters per sender and there
460 // is at least one left.
461 if ((new_length > space &&
462 (visible_count() > 2 ||
463 (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) {
464 RemoveLastAddedSender();
465 break;
466 }
467 length = new_length;
468 }
469
470 if (length > space) {
471 int max = ComputeSpacePerSender(space);
472 for (size_t i = 0; i < senders_.size(); ++i) {
473 if (senders_[i]->visible()) {
474 senders_[i]->set_space(max);
475 }
476 }
477 }
478
479 // Now construct the actual html
480 std::string html_list;
481 length = ConstructHtml(is_first_unread(), &html_list);
482 if (length > space) {
483 LOG(LS_WARNING) << "LENGTH: " << length << " exceeds "
484 << space << " " << html_list;
485 }
486 return html_list;
487 }
488
489 private:
490 int ComputeSpacePerSender(int space) const {
491 // Why the "- 2"? To allow for the " .. " which may occur
492 // after the names, and no matter what always allow at least
493 // 2 characters per sender.
494 return talk_base::_max<int>(space / visible_count() - 2, 2);
495 }
496
497 // Finds the next sender that should be added to the "from" list
498 // and sets it to visible.
499 //
500 // This method may be called until it returns false or
501 // until RemoveLastAddedSender is called.
502 bool AddNextSender() {
503 // The progression is:
504 // 1. Add the person who started the thread, which is the first message.
505 // 2. Add the first unread message (unless it has already been added).
506 // 3. Add the last message (unless it has already been added).
507 // 4. Add the message that is just before the last message processed
508 // (unless it has already been added).
509 // If there is no message (i.e. at index -1), return false.
510 //
511 // Typically, this method is called until it returns false or
512 // all of the space available is used.
513 switch (state_) {
514 case INITIAL_STATE:
515 state_ = FIRST_MESSAGE;
516 index_ = 0;
517 // If the server behaves odd and doesn't send us any senders,
518 // do something graceful.
519 if (senders_.size() == 0) {
520 return false;
521 }
522 break;
523
524 case FIRST_MESSAGE:
525 if (first_unread_index_ >= 0) {
526 state_ = FIRST_UNREAD_MESSAGE;
527 index_ = first_unread_index_;
528 break;
529 }
530 // fall through
531 case FIRST_UNREAD_MESSAGE:
532 state_ = LAST_MESSAGE;
533 index_ = senders_.size() - 1;
534 break;
535
536 case LAST_MESSAGE:
537 case PREVIOUS_MESSAGE:
538 state_ = PREVIOUS_MESSAGE;
539 index_--;
540 break;
541
542 case REMOVED_MESSAGE:
543 default:
544 ASSERT(false);
545 return false;
546 }
547
548 if (index_ < 0) {
549 return false;
550 }
551
552 if (!senders_[index_]->visible()) {
553 added_.push(index_);
554 senders_[index_]->set_visible(true);
555 }
556 return true;
557 }
558
559 // Makes the last added sender not visible.
560 //
561 // May be called while visible_count() > 0.
562 void RemoveLastAddedSender() {
563 state_ = REMOVED_MESSAGE;
564 int index = added_.top();
565 added_.pop();
566 senders_[index]->set_visible(false);
567 }
568
569 // Constructs the html of the SenderList and returns the length of the
570 // visible text.
571 //
572 // The algorithm simply walks down the list of Senders, appending
573 // the html for each visible sender, and adding ellipsis or commas
574 // in between, whichever is appropriate.
575 //
576 // html Filled with html. Maybe NULL if the html doesn't
577 // need to be constructed yet (useful for simply
578 // determining the length of the visible text).
579 //
580 // returns The approximate visible length of the html.
581 int ConstructHtml(bool first_is_unread,
582 std::string* html) const {
583 if (senders_.empty()) {
584 return 0;
585 }
586
587 int length = 0;
588
589 // The first is always visible
590 const SenderFormatter* sender = senders_[0];
591 const std::string& originator_name = sender->name();
592 length += originator_name.length();
593 if (html) {
594 html->append(FormatName(originator_name, first_is_unread));
595 }
596
597 bool elided = false;
598 const char* between = "";
599 for (size_t i = 1; i < senders_.size(); i++) {
600 sender = senders_[i];
601
602 if (sender->visible()) {
603 // Handle the separator
604 between = elided ? "&nbsp;" : kNormalSeparator;
605 // Ignore the , for length because it is so narrow,
606 // so in both cases above the space is the only things
607 // that counts for spaces.
608 length++;
609
610 // Handle the name
611 const std::string name = sender->name();
612 length += name.size();
613
614 // Construct the html
615 if (html) {
616 html->append(between);
617 html->append(FormatName(name, sender->is_unread()));
618 }
619 elided = false;
620 } else if (!elided) {
621 between = kEllidedSeparator;
622 length += 2; // ".." is narrow
623 if (html) {
624 html->append(between);
625 }
626 elided = true;
627 }
628 }
629 return length;
630 }
631
632 bool is_first_unread() const {
633 return !are_any_read_;
634 }
635
636 size_t visible_count() const {
637 return added_.size();
638 }
639
640 enum MessageState {
641 INITIAL_STATE,
642 FIRST_MESSAGE,
643 FIRST_UNREAD_MESSAGE,
644 LAST_MESSAGE,
645 PREVIOUS_MESSAGE,
646 REMOVED_MESSAGE,
647 };
648
649 // What state we were in during the last "stateful" function call.
650 MessageState state_;
651 bool are_any_read_;
652 std::vector<SenderFormatter*> senders_;
653 std::stack<int> added_;
654 int index_;
655 int first_unread_index_;
656 DISALLOW_COPY_AND_ASSIGN(SenderFormatterList);
657 };
658
659
660 std::string GetSenderHtml(const MailSenderList& sender_list,
661 int message_count,
662 const std::string& me_address,
663 int space) {
664 // There has to be at least 9 spaces to show something reasonable.
665 ASSERT(space >= 10);
666 std::string count_html;
667 if (message_count > 1) {
668 std::string count(IntToString(message_count));
669 space -= (count.size());
670 count_html.append("&nbsp;(");
671 count_html.append(count);
672 count_html.append(")");
673 // Reduce space due to parenthesis and &nbsp;.
674 space -= 2;
675 }
676
677 SenderFormatterList senders(sender_list, me_address);
678 std::string html_list(senders.GetHtml(space));
679 html_list.append(count_html);
680 return html_list;
681 }
682 } // namespace notifier
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698