Index: chrome/browser/sync/notifier/communicator/mailbox.cc |
=================================================================== |
--- chrome/browser/sync/notifier/communicator/mailbox.cc (revision 46353) |
+++ chrome/browser/sync/notifier/communicator/mailbox.cc (working copy) |
@@ -1,675 +0,0 @@ |
-// Copyright (c) 2009 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "chrome/browser/sync/notifier/communicator/mailbox.h" |
- |
-#include <assert.h> |
-#include <stdlib.h> |
- |
-#include <stack> |
-#include <vector> |
- |
-#include "chrome/browser/sync/notifier/base/string.h" |
-#include "chrome/browser/sync/notifier/base/utils.h" |
-#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h" |
-#include "talk/base/basictypes.h" |
-#include "talk/base/common.h" |
-#include "talk/base/stringutils.h" |
-#include "talk/xmllite/xmlelement.h" |
-#include "talk/xmpp/constants.h" |
- |
-namespace notifier { |
- |
-// Labels are a list of strings seperated by a '|' character. The '|' character |
-// is escaped with a backslash ('\\') and the backslash is also escaped with a |
-// backslash. |
-static void ParseLabelSet(const std::string& text, |
- MessageThread::StringSet* labels) { |
- const char* input_cur = text.c_str(); |
- const char* input_end = input_cur + text.size(); |
- char* result = new char[text.size() + 1]; |
- char* next_write = result; |
- |
- while (input_cur < input_end) { |
- if (*input_cur == '|') { |
- if (next_write != result) { |
- *next_write = '\0'; |
- labels->insert(std::string(result)); |
- next_write = result; |
- } |
- input_cur++; |
- continue; |
- } |
- |
- if (*input_cur == '\\') { |
- // Skip a character in the input and break if we are at the end. |
- input_cur++; |
- if (input_cur >= input_end) |
- break; |
- } |
- *next_write = *input_cur; |
- next_write++; |
- input_cur++; |
- } |
- |
- if (next_write != result) { |
- *next_write = '\0'; |
- labels->insert(std::string(result)); |
- } |
- |
- delete [] result; |
-} |
- |
-// ----------------------------------------------------------------------------- |
- |
-std::string MailAddress::safe_name() const { |
- if (!name().empty()) { |
- return name(); |
- } |
- |
- if (!address().empty()) { |
- size_t at = address().find('@'); |
- if (at == std::string::npos) { |
- return address(); |
- } |
- |
- if (at != 0) { |
- return address().substr(0, at); |
- } |
- } |
- |
- return std::string("(unknown)"); |
-} |
- |
-// ----------------------------------------------------------------------------- |
-MessageThread::~MessageThread() { |
- Clear(); |
-} |
- |
-void MessageThread::Clear() { |
- delete labels_; |
- labels_ = NULL; |
- |
- delete senders_; |
- senders_ = NULL; |
-} |
- |
-MessageThread& MessageThread::operator=(const MessageThread& r) { |
- if (&r != this) { |
- Clear(); |
- // Copy everything. |
- r.AssertValid(); |
- thread_id_ = r.thread_id_; |
- date64_ = r.date64_; |
- message_count_ = r.message_count_; |
- personal_level_ = r.personal_level_; |
- subject_ = r.subject_; |
- snippet_ = r.snippet_; |
- |
- if (r.labels_) |
- labels_ = new StringSet(*r.labels_); |
- else |
- labels_ = new StringSet; |
- if (r.senders_) |
- senders_ = new MailSenderList(*r.senders_); |
- else |
- senders_ = new MailSenderList; |
- } |
- AssertValid(); |
- return *this; |
-} |
- |
-MessageThread* MessageThread::CreateFromXML( |
- const buzz::XmlElement* src) { |
- MessageThread* info = new MessageThread(); |
- if (!info || !info->InitFromXml(src)) { |
- delete info; |
- return NULL; |
- } |
- return info; |
-} |
- |
-// Init from a chunk of XML. |
-bool MessageThread::InitFromXml(const buzz::XmlElement* src) { |
- labels_ = new StringSet; |
- senders_ = new MailSenderList; |
- |
- if (src->Name() != buzz::kQnMailThreadInfo) |
- return false; |
- |
- if (!ParseInt64Attr(src, buzz::kQnMailTid, &thread_id_)) |
- return false; |
- if (!ParseInt64Attr(src, buzz::kQnMailDate, &date64_)) |
- return false; |
- if (!ParseIntAttr(src, buzz::kQnMailMessages, &message_count_)) |
- return false; |
- if (!ParseIntAttr(src, buzz::kQnMailParticipation, &personal_level_)) |
- return false; |
- |
- const buzz::XmlElement* senders = src->FirstNamed(buzz::kQnMailSenders); |
- if (!senders) |
- return false; |
- for (const buzz::XmlElement* child = senders->FirstElement(); |
- child != NULL; |
- child = child->NextElement()) { |
- if (child->Name() != buzz::kQnMailSender) |
- continue; |
- std::string address; |
- if (!ParseStringAttr(child, buzz::kQnMailAddress, &address)) |
- continue; |
- std::string name; |
- ParseStringAttr(child, buzz::kQnMailName, &name); |
- bool originator = false; |
- ParseBoolAttr(child, buzz::kQnMailOriginator, &originator); |
- bool unread = false; |
- ParseBoolAttr(child, buzz::kQnMailUnread, &unread); |
- |
- senders_->push_back(MailSender(name, address, unread, originator)); |
- } |
- |
- const buzz::XmlElement* labels = src->FirstNamed(buzz::kQnMailLabels); |
- if (!labels) |
- return false; |
- ParseLabelSet(labels->BodyText(), labels_); |
- |
- const buzz::XmlElement* subject = src->FirstNamed(buzz::kQnMailSubject); |
- if (!subject) |
- return false; |
- subject_ = subject->BodyText(); |
- |
- const buzz::XmlElement* snippet = src->FirstNamed(buzz::kQnMailSnippet); |
- if (!snippet) |
- return false; |
- snippet_ = snippet->BodyText(); |
- |
- AssertValid(); |
- return true; |
-} |
- |
-bool MessageThread::starred() const { |
- return (labels_->find("^t") != labels_->end()); |
-} |
- |
-bool MessageThread::unread() const { |
- return (labels_->find("^u") != labels_->end()); |
-} |
- |
-#if defined(DEBUG) |
-// Non-debug version is inline and empty. |
-void MessageThread::AssertValid() const { |
- assert(thread_id_ != 0); |
- assert(senders_ != NULL); |
- // In some (odd) cases, gmail may send email with no sender. |
- // assert(!senders_->empty()); |
- assert(message_count_ > 0); |
- assert(labels_ != NULL); |
-} |
-#endif |
- |
-MailBox* MailBox::CreateFromXML(const buzz::XmlElement* src) { |
- MailBox* mail_box = new MailBox(); |
- if (!mail_box || !mail_box->InitFromXml(src)) { |
- delete mail_box; |
- return NULL; |
- } |
- return mail_box; |
-} |
- |
-// Init from a chunk of XML. |
-bool MailBox::InitFromXml(const buzz::XmlElement* src) { |
- if (src->Name() != buzz::kQnMailBox) |
- return false; |
- |
- if (!ParseIntAttr(src, buzz::kQnMailTotalMatched, &mailbox_size_)) |
- return false; |
- |
- estimate_ = false; |
- ParseBoolAttr(src, buzz::kQnMailTotalEstimate, &estimate_); |
- |
- first_index_ = 0; |
- ParseIntAttr(src, buzz::kQnMailFirstIndex, &first_index_); |
- |
- result_time_ = 0; |
- ParseInt64Attr(src, buzz::kQnMailResultTime, &result_time_); |
- |
- highest_thread_id_ = 0; |
- |
- const buzz::XmlElement* thread_element = src->FirstNamed(buzz::kQnMailThreadInfo); |
- while (thread_element) { |
- MessageThread* thread = MessageThread::CreateFromXML(thread_element); |
- if (thread) { |
- if (thread->thread_id() > highest_thread_id_) |
- highest_thread_id_ = thread->thread_id(); |
- threads_.push_back(MessageThreadPointer(thread)); |
- } |
- thread_element = thread_element->NextNamed(buzz::kQnMailThreadInfo); |
- } |
- return true; |
-} |
- |
-const size_t kMaxShortnameLength = 12; |
- |
-// Tip: If you extend this list of chars, do not include '-'. |
-const char name_delim[] = " ,.:;\'\"()[]{}<>*@"; |
- |
-class SenderFormatter { |
- public: |
- // sender should not be deleted while this class is in use. |
- SenderFormatter(const MailSender& sender, |
- const std::string& me_address) |
- : sender_(sender), |
- visible_(false), |
- short_format_(true), |
- space_(kMaxShortnameLength) { |
- me_ = talk_base::ascicmp(me_address.c_str(), |
- sender.address().c_str()) == 0; |
- } |
- |
- bool visible() const { |
- return visible_; |
- } |
- |
- bool is_unread() const { |
- return sender_.unread(); |
- } |
- |
- const std::string name() const { |
- return name_; |
- } |
- |
- void set_short_format(bool short_format) { |
- short_format_ = short_format; |
- UpdateName(); |
- } |
- |
- void set_visible(bool visible) { |
- visible_ = visible; |
- UpdateName(); |
- } |
- |
- void set_space(size_t space) { |
- space_ = space; |
- UpdateName(); |
- } |
- |
- private: |
- // Attempt to shorten to the first word in a person's name We could revisit |
- // and do better at international punctuation, but this is what cricket did, |
- // and it should be removed soon when gmail does the notification instead of |
- // us forming it on the client. |
- static void ShortenName(std::string* name) { |
- size_t start = name->find_first_not_of(name_delim); |
- if (start != std::string::npos && start > 0) { |
- name->erase(0, start); |
- } |
- start = name->find_first_of(name_delim); |
- if (start != std::string::npos) { |
- name->erase(start); |
- } |
- } |
- |
- void UpdateName() { |
- // Update the name if is going to be used. |
- if (!visible_) { |
- return; |
- } |
- |
- if (me_) { |
- name_ = "me"; |
- return; |
- } |
- |
- if (sender_.name().empty() && sender_.address().empty()) { |
- name_ = ""; |
- return; |
- } |
- |
- name_ = sender_.name(); |
- // Handle the case of no name or a name looks like an email address. When |
- // mail is sent to "Quality@example.com" <quality-team@example.com>, we |
- // shouldn't show "Quality@example.com" as the name. Instead, use the email |
- // address (without the @...) |
- if (name_.empty() || name_.find_first_of("@") != std::string::npos) { |
- name_ = sender_.address(); |
- size_t at_index = name_.find_first_of("@"); |
- if (at_index != std::string::npos) { |
- name_.erase(at_index); |
- } |
- } else if (short_format_) { |
- ShortenName(&name_); |
- } |
- |
- if (name_.empty()) { |
- name_ = "(unknown)"; |
- } |
- |
- // Abbreviate if too long. |
- if (name_.size() > space_) { |
- name_.replace(space_ - 1, std::string::npos, "."); |
- } |
- } |
- |
- const MailSender& sender_; |
- std::string name_; |
- bool visible_; |
- bool short_format_; |
- size_t space_; |
- bool me_; |
- DISALLOW_COPY_AND_ASSIGN(SenderFormatter); |
-}; |
- |
-const char kNormalSeparator[] = ", "; |
-const char kEllidedSeparator[] = " .."; |
- |
-std::string FormatName(const std::string& name, bool bold) { |
- std::string formatted_name; |
- if (bold) { |
- formatted_name.append("<b>"); |
- } |
- formatted_name.append(HtmlEncode(name)); |
- if (bold) { |
- formatted_name.append("</b>"); |
- } |
- return formatted_name; |
-} |
- |
-class SenderFormatterList { |
- public: |
- // sender_list must not be deleted while this class is being used. |
- SenderFormatterList(const MailSenderList& sender_list, |
- const std::string& me_address) |
- : state_(INITIAL_STATE), |
- are_any_read_(false), |
- index_(-1), |
- first_unread_index_(-1) { |
- // Add all read messages. |
- const MailSender* originator = NULL; |
- bool any_unread = false; |
- for (size_t i = 0; i < sender_list.size(); ++i) { |
- if (sender_list[i].originator()) { |
- originator = &sender_list[i]; |
- } |
- if (sender_list[i].unread()) { |
- any_unread = true; |
- continue; |
- } |
- are_any_read_ = true; |
- if (!sender_list[i].originator()) { |
- senders_.push_back(new SenderFormatter(sender_list[i], |
- me_address)); |
- } |
- } |
- |
- // There may not be an orignator (if there no senders). |
- if (originator) { |
- senders_.insert(senders_.begin(), new SenderFormatter(*originator, |
- me_address)); |
- } |
- |
- // Add all unread messages. |
- if (any_unread) { |
- // It should be rare, but there may be cases when all of the senders |
- // appear to have read the message. |
- first_unread_index_ = is_first_unread() ? 0 : senders_.size(); |
- for (size_t i = 0; i < sender_list.size(); ++i) { |
- if (!sender_list[i].unread()) { |
- continue; |
- } |
- // Don't add the originator if it is already at the start of the |
- // "unread" list. |
- if (sender_list[i].originator() && is_first_unread()) { |
- continue; |
- } |
- senders_.push_back(new SenderFormatter(sender_list[i], me_address)); |
- } |
- } |
- } |
- |
- ~SenderFormatterList() { |
- CleanupSequence(&senders_); |
- } |
- |
- std::string GetHtml(int space) { |
- index_ = -1; |
- state_ = INITIAL_STATE; |
- while (!added_.empty()) { |
- added_.pop(); |
- } |
- |
- // If there is only one sender, let it take up all of the space. |
- if (senders_.size() == 1) { |
- senders_[0]->set_space(space); |
- senders_[0]->set_short_format(false); |
- } |
- |
- int length = 1; |
- // Add as many senders as we can in the given space. Computes the visible |
- // length at each iteration, but does not construct the actual html. |
- while (length < space && AddNextSender()) { |
- int new_length = ConstructHtml(is_first_unread(), NULL); |
- // Remove names to avoid truncating |
- // * if there will be at least 2 left or |
- // * if the spacing <= 2 characters per sender and there |
- // is at least one left. |
- if ((new_length > space && |
- (visible_count() > 2 || |
- (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) { |
- RemoveLastAddedSender(); |
- break; |
- } |
- length = new_length; |
- } |
- |
- if (length > space) { |
- int max = ComputeSpacePerSender(space); |
- for (size_t i = 0; i < senders_.size(); ++i) { |
- if (senders_[i]->visible()) { |
- senders_[i]->set_space(max); |
- } |
- } |
- } |
- |
- // Now construct the actual html. |
- std::string html_list; |
- length = ConstructHtml(is_first_unread(), &html_list); |
- if (length > space) { |
- LOG(LS_WARNING) << "LENGTH: " << length << " exceeds " |
- << space << " " << html_list; |
- } |
- return html_list; |
- } |
- |
- private: |
- int ComputeSpacePerSender(int space) const { |
- // Why the "- 2"? To allow for the " .. " which may occur after the names, |
- // and no matter what always allow at least 2 characters per sender. |
- return talk_base::_max<int>(space / visible_count() - 2, 2); |
- } |
- |
- // Finds the next sender that should be added to the "from" list and sets it |
- // to visible. |
- // |
- // This method may be called until it returns false or until |
- // RemoveLastAddedSender is called. |
- bool AddNextSender() { |
- // The progression is: |
- // 1. Add the person who started the thread, which is the first message. |
- // 2. Add the first unread message (unless it has already been added). |
- // 3. Add the last message (unless it has already been added). |
- // 4. Add the message that is just before the last message processed |
- // (unless it has already been added). |
- // If there is no message (i.e. at index -1), return false. |
- // |
- // Typically, this method is called until it returns false or all of the |
- // space available is used. |
- switch (state_) { |
- case INITIAL_STATE: |
- state_ = FIRST_MESSAGE; |
- index_ = 0; |
- // If the server behaves odd and doesn't send us any senders, do |
- // something graceful. |
- if (senders_.size() == 0) { |
- return false; |
- } |
- break; |
- |
- case FIRST_MESSAGE: |
- if (first_unread_index_ >= 0) { |
- state_ = FIRST_UNREAD_MESSAGE; |
- index_ = first_unread_index_; |
- break; |
- } |
- // Fall through. |
- case FIRST_UNREAD_MESSAGE: |
- state_ = LAST_MESSAGE; |
- index_ = senders_.size() - 1; |
- break; |
- |
- case LAST_MESSAGE: |
- case PREVIOUS_MESSAGE: |
- state_ = PREVIOUS_MESSAGE; |
- index_--; |
- break; |
- |
- case REMOVED_MESSAGE: |
- default: |
- ASSERT(false); |
- return false; |
- } |
- |
- if (index_ < 0) { |
- return false; |
- } |
- |
- if (!senders_[index_]->visible()) { |
- added_.push(index_); |
- senders_[index_]->set_visible(true); |
- } |
- return true; |
- } |
- |
- // Makes the last added sender not visible. |
- // |
- // May be called while visible_count() > 0. |
- void RemoveLastAddedSender() { |
- state_ = REMOVED_MESSAGE; |
- int index = added_.top(); |
- added_.pop(); |
- senders_[index]->set_visible(false); |
- } |
- |
- // Constructs the html of the SenderList and returns the length of the |
- // visible text. |
- // |
- // The algorithm simply walks down the list of Senders, appending the html |
- // for each visible sender, and adding ellipsis or commas in between, |
- // whichever is appropriate. |
- // |
- // html Filled with html. Maybe NULL if the html doesn't need to be |
- // constructed yet (useful for simply determining the length of the |
- // visible text). |
- // |
- // returns The approximate visible length of the html. |
- int ConstructHtml(bool first_is_unread, |
- std::string* html) const { |
- if (senders_.empty()) { |
- return 0; |
- } |
- |
- int length = 0; |
- |
- // The first is always visible. |
- const SenderFormatter* sender = senders_[0]; |
- const std::string& originator_name = sender->name(); |
- length += originator_name.length(); |
- if (html) { |
- html->append(FormatName(originator_name, first_is_unread)); |
- } |
- |
- bool elided = false; |
- const char* between = ""; |
- for (size_t i = 1; i < senders_.size(); i++) { |
- sender = senders_[i]; |
- |
- if (sender->visible()) { |
- // Handle the separator. |
- between = elided ? " " : kNormalSeparator; |
- // Ignore the , for length because it is so narrow, so in both cases |
- // above the space is the only things that counts for spaces. |
- length++; |
- |
- // Handle the name. |
- const std::string name = sender->name(); |
- length += name.size(); |
- |
- // Construct the html. |
- if (html) { |
- html->append(between); |
- html->append(FormatName(name, sender->is_unread())); |
- } |
- elided = false; |
- } else if (!elided) { |
- between = kEllidedSeparator; |
- length += 2; // ".." is narrow. |
- if (html) { |
- html->append(between); |
- } |
- elided = true; |
- } |
- } |
- return length; |
- } |
- |
- bool is_first_unread() const { |
- return !are_any_read_; |
- } |
- |
- size_t visible_count() const { |
- return added_.size(); |
- } |
- |
- enum MessageState { |
- INITIAL_STATE, |
- FIRST_MESSAGE, |
- FIRST_UNREAD_MESSAGE, |
- LAST_MESSAGE, |
- PREVIOUS_MESSAGE, |
- REMOVED_MESSAGE, |
- }; |
- |
- // What state we were in during the last "stateful" function call. |
- MessageState state_; |
- bool are_any_read_; |
- std::vector<SenderFormatter*> senders_; |
- std::stack<int> added_; |
- int index_; |
- int first_unread_index_; |
- DISALLOW_COPY_AND_ASSIGN(SenderFormatterList); |
-}; |
- |
- |
-std::string GetSenderHtml(const MailSenderList& sender_list, |
- int message_count, |
- const std::string& me_address, |
- int space) { |
- // There has to be at least 9 spaces to show something reasonable. |
- ASSERT(space >= 10); |
- std::string count_html; |
- if (message_count > 1) { |
- std::string count(IntToString(message_count)); |
- space -= (count.size()); |
- count_html.append(" ("); |
- count_html.append(count); |
- count_html.append(")"); |
- // Reduce space due to parenthesis and . |
- space -= 2; |
- } |
- |
- SenderFormatterList senders(sender_list, me_address); |
- std::string html_list(senders.GetHtml(space)); |
- html_list.append(count_html); |
- return html_list; |
-} |
- |
-} // namespace notifier |