| Index: ios/chrome/browser/reading_list/reading_list_entry.cc | 
| diff --git a/ios/chrome/browser/reading_list/reading_list_entry.cc b/ios/chrome/browser/reading_list/reading_list_entry.cc | 
| index bea3f83d348ca4aca4cee98b19fed190764e968f..eed379aee645bc2c9a1682628f10003d8efbeb5b 100644 | 
| --- a/ios/chrome/browser/reading_list/reading_list_entry.cc | 
| +++ b/ios/chrome/browser/reading_list/reading_list_entry.cc | 
| @@ -4,7 +4,11 @@ | 
|  | 
| #include "ios/chrome/browser/reading_list/reading_list_entry.h" | 
|  | 
| +#include "base/json/json_string_value_serializer.h" | 
| #include "base/memory/ptr_util.h" | 
| +#include "components/sync/protocol/reading_list_specifics.pb.h" | 
| +#include "ios/chrome/browser/reading_list/proto/reading_list.pb.h" | 
| +#include "net/base/backoff_entry_serializer.h" | 
|  | 
| // The backoff time is the following: 10min, 10min, 1h, 2h, 2h..., starting | 
| // after the first failure. | 
| @@ -39,15 +43,42 @@ ReadingListEntry::ReadingListEntry(const GURL& url, const std::string& title) | 
| ReadingListEntry::ReadingListEntry(const GURL& url, | 
| const std::string& title, | 
| std::unique_ptr<net::BackoffEntry> backoff) | 
| +    : ReadingListEntry(url, | 
| +                       title, | 
| +                       0, | 
| +                       0, | 
| +                       WAITING, | 
| +                       GURL(), | 
| +                       0, | 
| +                       std::move(backoff)) {} | 
| + | 
| +ReadingListEntry::ReadingListEntry( | 
| +    const GURL& url, | 
| +    const std::string& title, | 
| +    int64_t creation_time, | 
| +    int64_t update_time, | 
| +    ReadingListEntry::DistillationState distilled_state, | 
| +    const GURL& distilled_url, | 
| +    int failed_download_counter, | 
| +    std::unique_ptr<net::BackoffEntry> backoff) | 
| : url_(url), | 
| title_(title), | 
| -      distilled_state_(WAITING), | 
| -      failed_download_counter_(0) { | 
| +      distilled_url_(distilled_url), | 
| +      distilled_state_(distilled_state), | 
| +      failed_download_counter_(failed_download_counter), | 
| +      creation_time_us_(creation_time), | 
| +      update_time_us_(update_time) { | 
| if (backoff) { | 
| backoff_ = std::move(backoff); | 
| } else { | 
| backoff_ = base::MakeUnique<net::BackoffEntry>(&kBackoffPolicy); | 
| } | 
| +  if (creation_time_us_ == 0) { | 
| +    DCHECK(update_time_us_ == 0); | 
| +    creation_time_us_ = | 
| +        (base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds(); | 
| +    update_time_us_ = creation_time_us_; | 
| +  } | 
| DCHECK(!url.is_empty()); | 
| DCHECK(url.is_valid()); | 
| } | 
| @@ -58,7 +89,9 @@ ReadingListEntry::ReadingListEntry(ReadingListEntry&& entry) | 
| distilled_url_(std::move(entry.distilled_url_)), | 
| distilled_state_(std::move(entry.distilled_state_)), | 
| backoff_(std::move(entry.backoff_)), | 
| -      failed_download_counter_(std::move(entry.failed_download_counter_)) {} | 
| +      failed_download_counter_(std::move(entry.failed_download_counter_)), | 
| +      creation_time_us_(std::move(entry.creation_time_us_)), | 
| +      update_time_us_(std::move(entry.update_time_us_)) {} | 
|  | 
| ReadingListEntry::~ReadingListEntry() {} | 
|  | 
| @@ -93,6 +126,8 @@ ReadingListEntry& ReadingListEntry::operator=(ReadingListEntry&& other) { | 
| distilled_state_ = std::move(other.distilled_state_); | 
| backoff_ = std::move(other.backoff_); | 
| failed_download_counter_ = std::move(other.failed_download_counter_); | 
| +  creation_time_us_ = std::move(other.creation_time_us_); | 
| +  update_time_us_ = std::move(other.update_time_us_); | 
| return *this; | 
| } | 
|  | 
| @@ -126,3 +161,209 @@ void ReadingListEntry::SetDistilledState(DistillationState distilled_state) { | 
| distilled_state_ = distilled_state; | 
| distilled_url_ = GURL(); | 
| } | 
| + | 
| +int64_t ReadingListEntry::UpdateTime() const { | 
| +  return update_time_us_; | 
| +} | 
| + | 
| +int64_t ReadingListEntry::CreationTime() const { | 
| +  return creation_time_us_; | 
| +} | 
| + | 
| +void ReadingListEntry::MarkEntryUpdated() { | 
| +  update_time_us_ = | 
| +      (base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds(); | 
| +} | 
| + | 
| +// static | 
| +std::unique_ptr<ReadingListEntry> ReadingListEntry::FromReadingListLocal( | 
| +    const reading_list::ReadingListLocal& pb_entry) { | 
| +  if (!pb_entry.has_url()) { | 
| +    return nullptr; | 
| +  } | 
| +  GURL url(pb_entry.url()); | 
| +  if (url.is_empty() || !url.is_valid()) { | 
| +    return nullptr; | 
| +  } | 
| +  std::string title; | 
| +  if (pb_entry.has_title()) { | 
| +    title = pb_entry.title(); | 
| +  } | 
| + | 
| +  int64_t creation_time_us = 0; | 
| +  if (pb_entry.has_creation_time_us()) { | 
| +    creation_time_us = pb_entry.creation_time_us(); | 
| +  } | 
| + | 
| +  int64_t update_time_us = 0; | 
| +  if (pb_entry.has_update_time_us()) { | 
| +    update_time_us = pb_entry.update_time_us(); | 
| +  } | 
| + | 
| +  ReadingListEntry::DistillationState distillation_state = | 
| +      ReadingListEntry::WAITING; | 
| +  if (pb_entry.has_distillation_state()) { | 
| +    switch (pb_entry.distillation_state()) { | 
| +      case reading_list::ReadingListLocal::WAITING: | 
| +        distillation_state = ReadingListEntry::WAITING; | 
| +        break; | 
| +      case reading_list::ReadingListLocal::PROCESSING: | 
| +        distillation_state = ReadingListEntry::PROCESSING; | 
| +        break; | 
| +      case reading_list::ReadingListLocal::PROCESSED: | 
| +        distillation_state = ReadingListEntry::PROCESSED; | 
| +        break; | 
| +      case reading_list::ReadingListLocal::WILL_RETRY: | 
| +        distillation_state = ReadingListEntry::WILL_RETRY; | 
| +        break; | 
| +      case reading_list::ReadingListLocal::ERROR: | 
| +        distillation_state = ReadingListEntry::ERROR; | 
| +        break; | 
| +    } | 
| +  } | 
| + | 
| +  GURL distilled_url; | 
| +  if (pb_entry.has_distilled_url()) { | 
| +    distilled_url = GURL(pb_entry.distilled_url()); | 
| +  } | 
| + | 
| +  int64_t failed_download_counter = 0; | 
| +  if (pb_entry.has_failed_download_counter()) { | 
| +    failed_download_counter = pb_entry.failed_download_counter(); | 
| +  } | 
| + | 
| +  std::unique_ptr<net::BackoffEntry> backoff; | 
| +  if (pb_entry.has_backoff()) { | 
| +    JSONStringValueDeserializer deserializer(pb_entry.backoff()); | 
| +    std::unique_ptr<base::Value> value( | 
| +        deserializer.Deserialize(nullptr, nullptr)); | 
| +    if (value) { | 
| +      backoff = net::BackoffEntrySerializer::DeserializeFromValue( | 
| +          *value, &kBackoffPolicy, nullptr, base::Time::Now()); | 
| +    } | 
| +  } | 
| + | 
| +  return base::WrapUnique<ReadingListEntry>(new ReadingListEntry( | 
| +      url, title, creation_time_us, update_time_us, distillation_state, | 
| +      distilled_url, failed_download_counter, std::move(backoff))); | 
| +} | 
| + | 
| +// static | 
| +std::unique_ptr<ReadingListEntry> ReadingListEntry::FromReadingListSpecifics( | 
| +    const sync_pb::ReadingListSpecifics& pb_entry) { | 
| +  if (!pb_entry.has_url()) { | 
| +    return nullptr; | 
| +  } | 
| +  GURL url(pb_entry.url()); | 
| +  if (url.is_empty() || !url.is_valid()) { | 
| +    return nullptr; | 
| +  } | 
| +  std::string title; | 
| +  if (pb_entry.has_title()) { | 
| +    title = pb_entry.title(); | 
| +  } | 
| + | 
| +  int64_t creation_time_us = 0; | 
| +  if (pb_entry.has_creation_time_us()) { | 
| +    creation_time_us = pb_entry.creation_time_us(); | 
| +  } | 
| + | 
| +  int64_t update_time_us = 0; | 
| +  if (pb_entry.has_update_time_us()) { | 
| +    update_time_us = pb_entry.update_time_us(); | 
| +  } | 
| + | 
| +  return base::WrapUnique<ReadingListEntry>( | 
| +      new ReadingListEntry(url, title, creation_time_us, update_time_us, | 
| +                           WAITING, GURL(), 0, nullptr)); | 
| +} | 
| + | 
| +void ReadingListEntry::MergeLocalStateFrom(ReadingListEntry& other) { | 
| +  distilled_url_ = std::move(other.distilled_url_); | 
| +  distilled_state_ = std::move(other.distilled_state_); | 
| +  backoff_ = std::move(other.backoff_); | 
| +  failed_download_counter_ = std::move(other.failed_download_counter_); | 
| +} | 
| + | 
| +std::unique_ptr<reading_list::ReadingListLocal> | 
| +ReadingListEntry::AsReadingListLocal(bool read) const { | 
| +  std::unique_ptr<reading_list::ReadingListLocal> pb_entry = | 
| +      base::MakeUnique<reading_list::ReadingListLocal>(); | 
| + | 
| +  // URL is used as the key for the database and sync as there is only one entry | 
| +  // per URL. | 
| +  pb_entry->set_entry_id(URL().spec()); | 
| +  pb_entry->set_title(Title()); | 
| +  pb_entry->set_url(URL().spec()); | 
| +  pb_entry->set_creation_time_us(CreationTime()); | 
| +  pb_entry->set_update_time_us(UpdateTime()); | 
| + | 
| +  if (read) { | 
| +    pb_entry->set_status(reading_list::ReadingListLocal::READ); | 
| +  } else { | 
| +    pb_entry->set_status(reading_list::ReadingListLocal::UNREAD); | 
| +  } | 
| + | 
| +  reading_list::ReadingListLocal::DistillationState distilation_state; | 
| +  switch (DistilledState()) { | 
| +    case ReadingListEntry::WAITING: | 
| +      distilation_state = reading_list::ReadingListLocal::WAITING; | 
| +      break; | 
| +    case ReadingListEntry::PROCESSING: | 
| +      distilation_state = reading_list::ReadingListLocal::PROCESSING; | 
| +      break; | 
| +    case ReadingListEntry::PROCESSED: | 
| +      distilation_state = reading_list::ReadingListLocal::PROCESSED; | 
| +      break; | 
| +    case ReadingListEntry::WILL_RETRY: | 
| +      distilation_state = reading_list::ReadingListLocal::WILL_RETRY; | 
| +      break; | 
| +    case ReadingListEntry::ERROR: | 
| +      distilation_state = reading_list::ReadingListLocal::ERROR; | 
| +      break; | 
| +  } | 
| +  pb_entry->set_distillation_state(distilation_state); | 
| +  if (DistilledURL().is_valid()) { | 
| +    pb_entry->set_distilled_url(DistilledURL().spec()); | 
| +  } | 
| +  pb_entry->set_failed_download_counter(failed_download_counter_); | 
| + | 
| +  if (backoff_) { | 
| +    std::unique_ptr<base::Value> backoff = | 
| +        net::BackoffEntrySerializer::SerializeToValue(*backoff_, | 
| +                                                      base::Time::Now()); | 
| + | 
| +    std::string output; | 
| +    JSONStringValueSerializer serializer(&output); | 
| +    serializer.Serialize(*backoff); | 
| +    pb_entry->set_backoff(output); | 
| +  } | 
| +  return pb_entry; | 
| +} | 
| + | 
| +std::unique_ptr<sync_pb::ReadingListSpecifics> | 
| +ReadingListEntry::AsReadingListSpecifics(bool read) const { | 
| +  std::unique_ptr<sync_pb::ReadingListSpecifics> pb_entry = | 
| +      base::MakeUnique<sync_pb::ReadingListSpecifics>(); | 
| + | 
| +  // URL is used as the key for the database and sync as there is only one entry | 
| +  // per URL. | 
| +  pb_entry->set_entry_id(URL().spec()); | 
| +  pb_entry->set_title(Title()); | 
| +  pb_entry->set_url(URL().spec()); | 
| +  pb_entry->set_creation_time_us(CreationTime()); | 
| +  pb_entry->set_update_time_us(UpdateTime()); | 
| + | 
| +  if (read) { | 
| +    pb_entry->set_status(sync_pb::ReadingListSpecifics::READ); | 
| +  } else { | 
| +    pb_entry->set_status(sync_pb::ReadingListSpecifics::UNREAD); | 
| +  } | 
| + | 
| +  return pb_entry; | 
| +} | 
| + | 
| +bool ReadingListEntry::CompareEntryUpdateTime(const ReadingListEntry& lhs, | 
| +                                              const ReadingListEntry& rhs) { | 
| +  return lhs.UpdateTime() > rhs.UpdateTime(); | 
| +} | 
|  |