OLD | NEW |
| (Empty) |
1 // Copyright 2016 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 "components/reading_list/ios/reading_list_entry.h" | |
6 | |
7 #include "base/json/json_string_value_serializer.h" | |
8 #include "base/memory/ptr_util.h" | |
9 #include "components/reading_list/ios/offline_url_utils.h" | |
10 #include "components/reading_list/ios/proto/reading_list.pb.h" | |
11 #include "components/reading_list/ios/reading_list_store.h" | |
12 #include "components/sync/protocol/reading_list_specifics.pb.h" | |
13 #include "net/base/backoff_entry_serializer.h" | |
14 | |
15 namespace { | |
16 // Converts |time| to the number of microseconds since Jan 1st 1970. | |
17 int64_t TimeToUS(const base::Time& time) { | |
18 return (time - base::Time::UnixEpoch()).InMicroseconds(); | |
19 } | |
20 } | |
21 | |
22 // The backoff time is the following: 10min, 10min, 1h, 2h, 2h..., starting | |
23 // after the first failure. | |
24 const net::BackoffEntry::Policy ReadingListEntry::kBackoffPolicy = { | |
25 // Number of initial errors (in sequence) to ignore before applying | |
26 // exponential back-off rules. | |
27 2, | |
28 | |
29 // Initial delay for exponential back-off in ms. | |
30 10 * 60 * 1000, // 10 minutes. | |
31 | |
32 // Factor by which the waiting time will be multiplied. | |
33 6, | |
34 | |
35 // Fuzzing percentage. ex: 10% will spread requests randomly | |
36 // between 90%-100% of the calculated time. | |
37 0.1, // 10%. | |
38 | |
39 // Maximum amount of time we are willing to delay our request in ms. | |
40 2 * 3600 * 1000, // 2 hours. | |
41 | |
42 // Time to keep an entry from being discarded even when it | |
43 // has no significant state, -1 to never discard. | |
44 -1, | |
45 | |
46 true, // Don't use initial delay unless the last request was an error. | |
47 }; | |
48 | |
49 ReadingListEntry::ReadingListEntry(const GURL& url, | |
50 const std::string& title, | |
51 const base::Time& now) | |
52 : ReadingListEntry(url, title, now, nullptr){}; | |
53 | |
54 ReadingListEntry::ReadingListEntry(const GURL& url, | |
55 const std::string& title, | |
56 const base::Time& now, | |
57 std::unique_ptr<net::BackoffEntry> backoff) | |
58 : ReadingListEntry(url, | |
59 title, | |
60 UNSEEN, | |
61 TimeToUS(now), | |
62 0, | |
63 TimeToUS(now), | |
64 TimeToUS(now), | |
65 WAITING, | |
66 base::FilePath(), | |
67 GURL(), | |
68 0, | |
69 0, | |
70 0, | |
71 std::move(backoff)) {} | |
72 | |
73 ReadingListEntry::ReadingListEntry( | |
74 const GURL& url, | |
75 const std::string& title, | |
76 State state, | |
77 int64_t creation_time, | |
78 int64_t first_read_time, | |
79 int64_t update_time, | |
80 int64_t update_title_time, | |
81 ReadingListEntry::DistillationState distilled_state, | |
82 const base::FilePath& distilled_path, | |
83 const GURL& distilled_url, | |
84 int64_t distillation_time, | |
85 int64_t distillation_size, | |
86 int failed_download_counter, | |
87 std::unique_ptr<net::BackoffEntry> backoff) | |
88 : url_(url), | |
89 title_(title), | |
90 state_(state), | |
91 distilled_path_(distilled_path), | |
92 distilled_url_(distilled_url), | |
93 distilled_state_(distilled_state), | |
94 failed_download_counter_(failed_download_counter), | |
95 creation_time_us_(creation_time), | |
96 first_read_time_us_(first_read_time), | |
97 update_time_us_(update_time), | |
98 update_title_time_us_(update_title_time), | |
99 distillation_time_us_(distillation_time), | |
100 distillation_size_(distillation_size) { | |
101 if (backoff) { | |
102 backoff_ = std::move(backoff); | |
103 } else { | |
104 backoff_ = base::MakeUnique<net::BackoffEntry>(&kBackoffPolicy); | |
105 } | |
106 DCHECK(creation_time_us_); | |
107 DCHECK(update_time_us_); | |
108 DCHECK(update_title_time_us_); | |
109 DCHECK(!url.is_empty()); | |
110 DCHECK(url.is_valid()); | |
111 } | |
112 | |
113 ReadingListEntry::ReadingListEntry(ReadingListEntry&& entry) | |
114 : url_(std::move(entry.url_)), | |
115 title_(std::move(entry.title_)), | |
116 state_(std::move(entry.state_)), | |
117 distilled_path_(std::move(entry.distilled_path_)), | |
118 distilled_url_(std::move(entry.distilled_url_)), | |
119 distilled_state_(std::move(entry.distilled_state_)), | |
120 backoff_(std::move(entry.backoff_)), | |
121 failed_download_counter_(std::move(entry.failed_download_counter_)), | |
122 creation_time_us_(std::move(entry.creation_time_us_)), | |
123 first_read_time_us_(std::move(entry.first_read_time_us_)), | |
124 update_time_us_(std::move(entry.update_time_us_)), | |
125 update_title_time_us_(std::move(entry.update_title_time_us_)), | |
126 distillation_time_us_(std::move(entry.distillation_time_us_)), | |
127 distillation_size_(std::move(entry.distillation_size_)) {} | |
128 | |
129 ReadingListEntry::~ReadingListEntry() {} | |
130 | |
131 const GURL& ReadingListEntry::URL() const { | |
132 return url_; | |
133 } | |
134 | |
135 const std::string& ReadingListEntry::Title() const { | |
136 return title_; | |
137 } | |
138 | |
139 ReadingListEntry::DistillationState ReadingListEntry::DistilledState() const { | |
140 return distilled_state_; | |
141 } | |
142 | |
143 const base::FilePath& ReadingListEntry::DistilledPath() const { | |
144 return distilled_path_; | |
145 } | |
146 | |
147 const GURL& ReadingListEntry::DistilledURL() const { | |
148 return distilled_url_; | |
149 } | |
150 | |
151 int64_t ReadingListEntry::DistillationTime() const { | |
152 return distillation_time_us_; | |
153 } | |
154 | |
155 int64_t ReadingListEntry::DistillationSize() const { | |
156 return distillation_size_; | |
157 } | |
158 | |
159 base::TimeDelta ReadingListEntry::TimeUntilNextTry() const { | |
160 return backoff_->GetTimeUntilRelease(); | |
161 } | |
162 | |
163 int ReadingListEntry::FailedDownloadCounter() const { | |
164 return failed_download_counter_; | |
165 } | |
166 | |
167 ReadingListEntry& ReadingListEntry::operator=(ReadingListEntry&& other) { | |
168 url_ = std::move(other.url_); | |
169 title_ = std::move(other.title_); | |
170 distilled_path_ = std::move(other.distilled_path_); | |
171 distilled_url_ = std::move(other.distilled_url_); | |
172 distilled_state_ = std::move(other.distilled_state_); | |
173 backoff_ = std::move(other.backoff_); | |
174 state_ = std::move(other.state_); | |
175 failed_download_counter_ = std::move(other.failed_download_counter_); | |
176 creation_time_us_ = std::move(other.creation_time_us_); | |
177 first_read_time_us_ = std::move(other.first_read_time_us_); | |
178 update_time_us_ = std::move(other.update_time_us_); | |
179 update_title_time_us_ = std::move(other.update_title_time_us_); | |
180 distillation_time_us_ = std::move(other.distillation_time_us_); | |
181 distillation_size_ = std::move(other.distillation_size_); | |
182 return *this; | |
183 } | |
184 | |
185 bool ReadingListEntry::operator==(const ReadingListEntry& other) const { | |
186 return url_ == other.url_; | |
187 } | |
188 | |
189 void ReadingListEntry::SetTitle(const std::string& title, | |
190 const base::Time& now) { | |
191 title_ = title; | |
192 update_title_time_us_ = TimeToUS(now); | |
193 } | |
194 | |
195 void ReadingListEntry::SetRead(bool read, const base::Time& now) { | |
196 State previous_state = state_; | |
197 state_ = read ? READ : UNREAD; | |
198 if (state_ == previous_state) { | |
199 return; | |
200 } | |
201 if (FirstReadTime() == 0 && read) { | |
202 first_read_time_us_ = TimeToUS(now); | |
203 } | |
204 if (!(previous_state == UNSEEN && state_ == UNREAD)) { | |
205 // If changing UNSEEN -> UNREAD, entry is not marked updated to preserve | |
206 // order in Reading List View. | |
207 MarkEntryUpdated(now); | |
208 } | |
209 } | |
210 | |
211 bool ReadingListEntry::IsRead() const { | |
212 return state_ == READ; | |
213 } | |
214 | |
215 bool ReadingListEntry::HasBeenSeen() const { | |
216 return state_ != UNSEEN; | |
217 } | |
218 | |
219 void ReadingListEntry::SetDistilledInfo(const base::FilePath& path, | |
220 const GURL& distilled_url, | |
221 int64_t distilation_size, | |
222 const base::Time& distilation_time) { | |
223 DCHECK(!path.empty()); | |
224 DCHECK(distilled_url.is_valid()); | |
225 distilled_path_ = path; | |
226 distilled_state_ = PROCESSED; | |
227 distilled_url_ = distilled_url; | |
228 distillation_time_us_ = TimeToUS(distilation_time); | |
229 distillation_size_ = distilation_size; | |
230 backoff_->Reset(); | |
231 failed_download_counter_ = 0; | |
232 } | |
233 | |
234 void ReadingListEntry::SetDistilledState(DistillationState distilled_state) { | |
235 DCHECK(distilled_state != PROCESSED); // use SetDistilledPath instead. | |
236 DCHECK(distilled_state != WAITING); | |
237 // Increase time until next retry exponentially if the state change from a | |
238 // non-error state to an error state. | |
239 if ((distilled_state == WILL_RETRY || distilled_state == ERROR) && | |
240 distilled_state_ != WILL_RETRY && distilled_state_ != ERROR) { | |
241 backoff_->InformOfRequest(false); | |
242 failed_download_counter_++; | |
243 } | |
244 | |
245 distilled_state_ = distilled_state; | |
246 distilled_path_ = base::FilePath(); | |
247 distilled_url_ = GURL::EmptyGURL(); | |
248 } | |
249 | |
250 int64_t ReadingListEntry::UpdateTime() const { | |
251 return update_time_us_; | |
252 } | |
253 | |
254 int64_t ReadingListEntry::UpdateTitleTime() const { | |
255 return update_title_time_us_; | |
256 } | |
257 | |
258 int64_t ReadingListEntry::CreationTime() const { | |
259 return creation_time_us_; | |
260 } | |
261 | |
262 int64_t ReadingListEntry::FirstReadTime() const { | |
263 return first_read_time_us_; | |
264 } | |
265 | |
266 void ReadingListEntry::MarkEntryUpdated(const base::Time& now) { | |
267 update_time_us_ = TimeToUS(now); | |
268 } | |
269 | |
270 // static | |
271 std::unique_ptr<ReadingListEntry> ReadingListEntry::FromReadingListLocal( | |
272 const reading_list::ReadingListLocal& pb_entry, | |
273 const base::Time& now) { | |
274 if (!pb_entry.has_url()) { | |
275 return nullptr; | |
276 } | |
277 GURL url(pb_entry.url()); | |
278 if (url.is_empty() || !url.is_valid()) { | |
279 return nullptr; | |
280 } | |
281 std::string title; | |
282 if (pb_entry.has_title()) { | |
283 title = pb_entry.title(); | |
284 } | |
285 | |
286 int64_t creation_time_us = 0; | |
287 if (pb_entry.has_creation_time_us()) { | |
288 creation_time_us = pb_entry.creation_time_us(); | |
289 } else { | |
290 creation_time_us = (now - base::Time::UnixEpoch()).InMicroseconds(); | |
291 } | |
292 | |
293 int64_t first_read_time_us = 0; | |
294 if (pb_entry.has_first_read_time_us()) { | |
295 first_read_time_us = pb_entry.first_read_time_us(); | |
296 } | |
297 | |
298 int64_t update_time_us = creation_time_us; | |
299 if (pb_entry.has_update_time_us()) { | |
300 update_time_us = pb_entry.update_time_us(); | |
301 } | |
302 | |
303 int64_t update_title_time_us = creation_time_us; | |
304 if (pb_entry.has_update_title_time_us()) { | |
305 update_title_time_us = pb_entry.update_title_time_us(); | |
306 } | |
307 | |
308 State state = UNSEEN; | |
309 if (pb_entry.has_status()) { | |
310 switch (pb_entry.status()) { | |
311 case reading_list::ReadingListLocal::READ: | |
312 state = READ; | |
313 break; | |
314 case reading_list::ReadingListLocal::UNREAD: | |
315 state = UNREAD; | |
316 break; | |
317 case reading_list::ReadingListLocal::UNSEEN: | |
318 state = UNSEEN; | |
319 break; | |
320 } | |
321 } | |
322 | |
323 ReadingListEntry::DistillationState distillation_state = | |
324 ReadingListEntry::WAITING; | |
325 if (pb_entry.has_distillation_state()) { | |
326 switch (pb_entry.distillation_state()) { | |
327 case reading_list::ReadingListLocal::WAITING: | |
328 distillation_state = ReadingListEntry::WAITING; | |
329 break; | |
330 case reading_list::ReadingListLocal::PROCESSING: | |
331 distillation_state = ReadingListEntry::PROCESSING; | |
332 break; | |
333 case reading_list::ReadingListLocal::PROCESSED: | |
334 distillation_state = ReadingListEntry::PROCESSED; | |
335 break; | |
336 case reading_list::ReadingListLocal::WILL_RETRY: | |
337 distillation_state = ReadingListEntry::WILL_RETRY; | |
338 break; | |
339 case reading_list::ReadingListLocal::ERROR: | |
340 distillation_state = ReadingListEntry::ERROR; | |
341 break; | |
342 } | |
343 } | |
344 | |
345 base::FilePath distilled_path; | |
346 if (pb_entry.has_distilled_path()) { | |
347 distilled_path = base::FilePath(pb_entry.distilled_path()); | |
348 } | |
349 | |
350 GURL distilled_url; | |
351 if (pb_entry.has_distilled_url()) { | |
352 distilled_url = GURL(pb_entry.distilled_url()); | |
353 } | |
354 | |
355 int64_t distillation_time_us = 0; | |
356 if (pb_entry.has_distillation_time_us()) { | |
357 distillation_time_us = pb_entry.distillation_time_us(); | |
358 } | |
359 | |
360 int64_t distillation_size = 0; | |
361 if (pb_entry.has_distillation_size()) { | |
362 distillation_size = pb_entry.distillation_size(); | |
363 } | |
364 | |
365 int64_t failed_download_counter = 0; | |
366 if (pb_entry.has_failed_download_counter()) { | |
367 failed_download_counter = pb_entry.failed_download_counter(); | |
368 } | |
369 | |
370 std::unique_ptr<net::BackoffEntry> backoff; | |
371 if (pb_entry.has_backoff()) { | |
372 JSONStringValueDeserializer deserializer(pb_entry.backoff()); | |
373 std::unique_ptr<base::Value> value( | |
374 deserializer.Deserialize(nullptr, nullptr)); | |
375 if (value) { | |
376 backoff = net::BackoffEntrySerializer::DeserializeFromValue( | |
377 *value, &kBackoffPolicy, nullptr, now); | |
378 } | |
379 } | |
380 | |
381 return base::WrapUnique<ReadingListEntry>(new ReadingListEntry( | |
382 url, title, state, creation_time_us, first_read_time_us, update_time_us, | |
383 update_title_time_us, distillation_state, distilled_path, distilled_url, | |
384 distillation_time_us, distillation_size, failed_download_counter, | |
385 std::move(backoff))); | |
386 } | |
387 | |
388 // static | |
389 std::unique_ptr<ReadingListEntry> ReadingListEntry::FromReadingListSpecifics( | |
390 const sync_pb::ReadingListSpecifics& pb_entry, | |
391 const base::Time& now) { | |
392 if (!pb_entry.has_url()) { | |
393 return nullptr; | |
394 } | |
395 GURL url(pb_entry.url()); | |
396 if (url.is_empty() || !url.is_valid()) { | |
397 return nullptr; | |
398 } | |
399 std::string title; | |
400 if (pb_entry.has_title()) { | |
401 title = pb_entry.title(); | |
402 } | |
403 | |
404 int64_t creation_time_us = TimeToUS(now); | |
405 if (pb_entry.has_creation_time_us()) { | |
406 creation_time_us = pb_entry.creation_time_us(); | |
407 } | |
408 | |
409 int64_t first_read_time_us = 0; | |
410 if (pb_entry.has_first_read_time_us()) { | |
411 first_read_time_us = pb_entry.first_read_time_us(); | |
412 } | |
413 | |
414 int64_t update_time_us = creation_time_us; | |
415 if (pb_entry.has_update_time_us()) { | |
416 update_time_us = pb_entry.update_time_us(); | |
417 } | |
418 | |
419 int64_t update_title_time_us = creation_time_us; | |
420 if (pb_entry.has_update_title_time_us()) { | |
421 update_title_time_us = pb_entry.update_title_time_us(); | |
422 } | |
423 | |
424 State state = UNSEEN; | |
425 if (pb_entry.has_status()) { | |
426 switch (pb_entry.status()) { | |
427 case sync_pb::ReadingListSpecifics::READ: | |
428 state = READ; | |
429 break; | |
430 case sync_pb::ReadingListSpecifics::UNREAD: | |
431 state = UNREAD; | |
432 break; | |
433 case sync_pb::ReadingListSpecifics::UNSEEN: | |
434 state = UNSEEN; | |
435 break; | |
436 } | |
437 } | |
438 | |
439 return base::WrapUnique<ReadingListEntry>(new ReadingListEntry( | |
440 url, title, state, creation_time_us, first_read_time_us, update_time_us, | |
441 update_title_time_us, WAITING, base::FilePath(), GURL(), 0, 0, 0, | |
442 nullptr)); | |
443 } | |
444 | |
445 void ReadingListEntry::MergeWithEntry(const ReadingListEntry& other) { | |
446 #if !defined(NDEBUG) | |
447 // Checks that the result entry respects the sync order. | |
448 std::unique_ptr<sync_pb::ReadingListSpecifics> old_this_pb( | |
449 AsReadingListSpecifics()); | |
450 std::unique_ptr<sync_pb::ReadingListSpecifics> other_pb( | |
451 other.AsReadingListSpecifics()); | |
452 #endif | |
453 DCHECK(url_ == other.url_); | |
454 if (update_title_time_us_ < other.update_title_time_us_) { | |
455 // Take the most recent title updated. | |
456 title_ = std::move(other.title_); | |
457 update_title_time_us_ = std::move(other.update_title_time_us_); | |
458 } else if (update_title_time_us_ == other.update_title_time_us_) { | |
459 if (title_.compare(other.title_) < 0) { | |
460 // Take the last in alphabetical order or the longer one. | |
461 // This ensure empty string is replaced. | |
462 title_ = std::move(other.title_); | |
463 } | |
464 } | |
465 if (creation_time_us_ < other.creation_time_us_) { | |
466 creation_time_us_ = std::move(other.creation_time_us_); | |
467 first_read_time_us_ = std::move(other.first_read_time_us_); | |
468 } else if (creation_time_us_ == other.creation_time_us_) { | |
469 // The first_time_read_us from |other| is used if | |
470 // - this.first_time_read_us == 0: the entry was never read in this device. | |
471 // - this.first_time_read_us > other.first_time_read_us: the entry was | |
472 // first read on another device. | |
473 if (first_read_time_us_ == 0 || | |
474 (other.first_read_time_us_ != 0 && | |
475 other.first_read_time_us_ < first_read_time_us_)) { | |
476 first_read_time_us_ = std::move(other.first_read_time_us_); | |
477 } | |
478 } | |
479 if (update_time_us_ < other.update_time_us_) { | |
480 update_time_us_ = std::move(other.update_time_us_); | |
481 state_ = std::move(other.state_); | |
482 } else if (update_time_us_ == other.update_time_us_) { | |
483 if (state_ == UNSEEN) { | |
484 state_ = std::move(other.state_); | |
485 } else if (other.state_ == READ) { | |
486 state_ = std::move(other.state_); | |
487 } | |
488 } | |
489 #if !defined(NDEBUG) | |
490 std::unique_ptr<sync_pb::ReadingListSpecifics> new_this_pb( | |
491 AsReadingListSpecifics()); | |
492 DCHECK(ReadingListStore::CompareEntriesForSync(*old_this_pb, *new_this_pb)); | |
493 DCHECK(ReadingListStore::CompareEntriesForSync(*other_pb, *new_this_pb)); | |
494 #endif | |
495 } | |
496 | |
497 std::unique_ptr<reading_list::ReadingListLocal> | |
498 ReadingListEntry::AsReadingListLocal(const base::Time& now) const { | |
499 std::unique_ptr<reading_list::ReadingListLocal> pb_entry = | |
500 base::MakeUnique<reading_list::ReadingListLocal>(); | |
501 | |
502 // URL is used as the key for the database and sync as there is only one entry | |
503 // per URL. | |
504 pb_entry->set_entry_id(URL().spec()); | |
505 pb_entry->set_title(Title()); | |
506 pb_entry->set_url(URL().spec()); | |
507 pb_entry->set_creation_time_us(CreationTime()); | |
508 pb_entry->set_first_read_time_us(FirstReadTime()); | |
509 pb_entry->set_update_time_us(UpdateTime()); | |
510 pb_entry->set_update_title_time_us(UpdateTitleTime()); | |
511 | |
512 switch (state_) { | |
513 case READ: | |
514 pb_entry->set_status(reading_list::ReadingListLocal::READ); | |
515 break; | |
516 case UNREAD: | |
517 pb_entry->set_status(reading_list::ReadingListLocal::UNREAD); | |
518 break; | |
519 case UNSEEN: | |
520 pb_entry->set_status(reading_list::ReadingListLocal::UNSEEN); | |
521 break; | |
522 } | |
523 | |
524 reading_list::ReadingListLocal::DistillationState distilation_state; | |
525 switch (DistilledState()) { | |
526 case ReadingListEntry::WAITING: | |
527 distilation_state = reading_list::ReadingListLocal::WAITING; | |
528 break; | |
529 case ReadingListEntry::PROCESSING: | |
530 distilation_state = reading_list::ReadingListLocal::PROCESSING; | |
531 break; | |
532 case ReadingListEntry::PROCESSED: | |
533 distilation_state = reading_list::ReadingListLocal::PROCESSED; | |
534 break; | |
535 case ReadingListEntry::WILL_RETRY: | |
536 distilation_state = reading_list::ReadingListLocal::WILL_RETRY; | |
537 break; | |
538 case ReadingListEntry::ERROR: | |
539 distilation_state = reading_list::ReadingListLocal::ERROR; | |
540 break; | |
541 } | |
542 pb_entry->set_distillation_state(distilation_state); | |
543 if (!DistilledPath().empty()) { | |
544 pb_entry->set_distilled_path(DistilledPath().value()); | |
545 } | |
546 if (DistilledURL().is_valid()) { | |
547 pb_entry->set_distilled_url(DistilledURL().spec()); | |
548 } | |
549 if (DistillationTime()) { | |
550 pb_entry->set_distillation_time_us(DistillationTime()); | |
551 } | |
552 if (DistillationSize()) { | |
553 pb_entry->set_distillation_size(DistillationSize()); | |
554 } | |
555 | |
556 pb_entry->set_failed_download_counter(failed_download_counter_); | |
557 | |
558 if (backoff_) { | |
559 std::unique_ptr<base::Value> backoff = | |
560 net::BackoffEntrySerializer::SerializeToValue(*backoff_, now); | |
561 | |
562 std::string output; | |
563 JSONStringValueSerializer serializer(&output); | |
564 serializer.Serialize(*backoff); | |
565 pb_entry->set_backoff(output); | |
566 } | |
567 return pb_entry; | |
568 } | |
569 | |
570 std::unique_ptr<sync_pb::ReadingListSpecifics> | |
571 ReadingListEntry::AsReadingListSpecifics() const { | |
572 std::unique_ptr<sync_pb::ReadingListSpecifics> pb_entry = | |
573 base::MakeUnique<sync_pb::ReadingListSpecifics>(); | |
574 | |
575 // URL is used as the key for the database and sync as there is only one entry | |
576 // per URL. | |
577 pb_entry->set_entry_id(URL().spec()); | |
578 pb_entry->set_title(Title()); | |
579 pb_entry->set_url(URL().spec()); | |
580 pb_entry->set_creation_time_us(CreationTime()); | |
581 pb_entry->set_first_read_time_us(FirstReadTime()); | |
582 pb_entry->set_update_time_us(UpdateTime()); | |
583 pb_entry->set_update_title_time_us(UpdateTitleTime()); | |
584 | |
585 switch (state_) { | |
586 case READ: | |
587 pb_entry->set_status(sync_pb::ReadingListSpecifics::READ); | |
588 break; | |
589 case UNREAD: | |
590 pb_entry->set_status(sync_pb::ReadingListSpecifics::UNREAD); | |
591 break; | |
592 case UNSEEN: | |
593 pb_entry->set_status(sync_pb::ReadingListSpecifics::UNSEEN); | |
594 break; | |
595 } | |
596 | |
597 return pb_entry; | |
598 } | |
OLD | NEW |