| 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 5ff3dbc35c7ea75c792c3d13c8304fac99b854f5..b44816d1d7b5fbc297f711fc64cc2fed42fad5d8 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" | 
|  | 
| namespace { | 
| // URL used to open offline pages. | 
| @@ -45,15 +49,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, | 
| +                       base::FilePath(), | 
| +                       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 base::FilePath& distilled_path, | 
| +    int failed_download_counter, | 
| +    std::unique_ptr<net::BackoffEntry> backoff) | 
| : url_(url), | 
| title_(title), | 
| -      distilled_state_(WAITING), | 
| -      failed_download_counter_(0) { | 
| +      distilled_path_(distilled_path), | 
| +      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()); | 
| } | 
| @@ -64,7 +95,9 @@ ReadingListEntry::ReadingListEntry(ReadingListEntry&& entry) | 
| distilled_path_(std::move(entry.distilled_path_)), | 
| 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() {} | 
|  | 
| @@ -106,6 +139,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; | 
| } | 
|  | 
| @@ -139,3 +174,209 @@ void ReadingListEntry::SetDistilledState(DistillationState distilled_state) { | 
| distilled_state_ = distilled_state; | 
| distilled_path_ = base::FilePath(); | 
| } | 
| + | 
| +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; | 
| +    } | 
| +  } | 
| + | 
| +  base::FilePath distilled_path; | 
| +  if (pb_entry.has_distilled_path()) { | 
| +    distilled_path = base::FilePath(pb_entry.distilled_path()); | 
| +  } | 
| + | 
| +  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_path, 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, base::FilePath(), 0, nullptr)); | 
| +} | 
| + | 
| +void ReadingListEntry::MergeLocalStateFrom(ReadingListEntry& other) { | 
| +  distilled_path_ = std::move(other.distilled_path_); | 
| +  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 (!DistilledPath().empty()) { | 
| +    pb_entry->set_distilled_path(DistilledPath().value()); | 
| +  } | 
| +  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(); | 
| +} | 
|  |