| Index: chrome/browser/sync/notifier/communicator/mailbox.cc
|
| ===================================================================
|
| --- chrome/browser/sync/notifier/communicator/mailbox.cc (revision 0)
|
| +++ chrome/browser/sync/notifier/communicator/mailbox.cc (revision 0)
|
| @@ -0,0 +1,682 @@
|
| +// 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());
|
| +}
|
| +
|
| +#ifdef _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
|
|
|
| Property changes on: chrome\browser\sync\notifier\communicator\mailbox.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|