OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "chrome/browser/sync/engine/build_commit_command.h" | |
6 | |
7 #include <limits> | |
8 #include <set> | |
9 #include <string> | |
10 #include <vector> | |
11 | |
12 #include "base/string_util.h" | |
13 #include "chrome/browser/sync/engine/syncer_proto_util.h" | |
14 #include "chrome/browser/sync/sessions/sync_session.h" | |
15 #include "chrome/browser/sync/syncable/syncable.h" | |
16 #include "chrome/browser/sync/syncable/syncable_changes_version.h" | |
17 #include "chrome/browser/sync/util/time.h" | |
18 #include "sync/protocol/bookmark_specifics.pb.h" | |
19 | |
20 using std::set; | |
21 using std::string; | |
22 using std::vector; | |
23 using syncable::Entry; | |
24 using syncable::IS_DEL; | |
25 using syncable::SERVER_POSITION_IN_PARENT; | |
26 using syncable::IS_UNAPPLIED_UPDATE; | |
27 using syncable::IS_UNSYNCED; | |
28 using syncable::Id; | |
29 using syncable::MutableEntry; | |
30 using syncable::SPECIFICS; | |
31 using syncable::UNSPECIFIED; | |
32 | |
33 namespace browser_sync { | |
34 | |
35 using sessions::SyncSession; | |
36 | |
37 // static | |
38 int64 BuildCommitCommand::GetFirstPosition() { | |
39 return std::numeric_limits<int64>::min(); | |
40 } | |
41 | |
42 // static | |
43 int64 BuildCommitCommand::GetLastPosition() { | |
44 return std::numeric_limits<int64>::max(); | |
45 } | |
46 | |
47 // static | |
48 int64 BuildCommitCommand::GetGap() { | |
49 return 1LL << 20; | |
50 } | |
51 | |
52 BuildCommitCommand::BuildCommitCommand() {} | |
53 BuildCommitCommand::~BuildCommitCommand() {} | |
54 | |
55 void BuildCommitCommand::AddExtensionsActivityToMessage( | |
56 SyncSession* session, CommitMessage* message) { | |
57 // We only send ExtensionsActivity to the server if bookmarks are being | |
58 // committed. | |
59 ExtensionsActivityMonitor* monitor = session->context()->extensions_monitor(); | |
60 if (!session->status_controller().HasBookmarkCommitActivity()) { | |
61 // Return the records to the activity monitor. | |
62 monitor->PutRecords(session->extensions_activity()); | |
63 session->mutable_extensions_activity()->clear(); | |
64 return; | |
65 } | |
66 const ExtensionsActivityMonitor::Records& records = | |
67 session->extensions_activity(); | |
68 for (ExtensionsActivityMonitor::Records::const_iterator it = records.begin(); | |
69 it != records.end(); ++it) { | |
70 sync_pb::ChromiumExtensionsActivity* activity_message = | |
71 message->add_extensions_activity(); | |
72 activity_message->set_extension_id(it->second.extension_id); | |
73 activity_message->set_bookmark_writes_since_last_commit( | |
74 it->second.bookmark_write_count); | |
75 } | |
76 } | |
77 | |
78 namespace { | |
79 void SetEntrySpecifics(MutableEntry* meta_entry, SyncEntity* sync_entry) { | |
80 // Add the new style extension and the folder bit. | |
81 sync_entry->mutable_specifics()->CopyFrom(meta_entry->Get(SPECIFICS)); | |
82 sync_entry->set_folder(meta_entry->Get(syncable::IS_DIR)); | |
83 | |
84 DCHECK(meta_entry->GetModelType() == sync_entry->GetModelType()); | |
85 } | |
86 } // namespace | |
87 | |
88 SyncerError BuildCommitCommand::ExecuteImpl(SyncSession* session) { | |
89 ClientToServerMessage message; | |
90 message.set_share(session->context()->account_name()); | |
91 message.set_message_contents(ClientToServerMessage::COMMIT); | |
92 | |
93 CommitMessage* commit_message = message.mutable_commit(); | |
94 commit_message->set_cache_guid( | |
95 session->write_transaction()->directory()->cache_guid()); | |
96 AddExtensionsActivityToMessage(session, commit_message); | |
97 SyncerProtoUtil::AddRequestBirthday( | |
98 session->write_transaction()->directory(), &message); | |
99 | |
100 // Cache previously computed position values. Because |commit_ids| | |
101 // is already in sibling order, we should always hit this map after | |
102 // the first sibling in a consecutive run of commit items. The | |
103 // entries in this map are (low, high) values describing the | |
104 // space of positions that are immediate successors of the item | |
105 // whose ID is the map's key. | |
106 std::map<Id, std::pair<int64, int64> > position_map; | |
107 | |
108 const vector<Id>& commit_ids = session->status_controller().commit_ids(); | |
109 for (size_t i = 0; i < commit_ids.size(); i++) { | |
110 Id id = commit_ids[i]; | |
111 SyncEntity* sync_entry = | |
112 static_cast<SyncEntity*>(commit_message->add_entries()); | |
113 sync_entry->set_id(id); | |
114 MutableEntry meta_entry(session->write_transaction(), | |
115 syncable::GET_BY_ID, | |
116 id); | |
117 CHECK(meta_entry.good()); | |
118 // This is the only change we make to the entry in this function. | |
119 meta_entry.Put(syncable::SYNCING, true); | |
120 | |
121 DCHECK(0 != session->routing_info().count(meta_entry.GetModelType())) | |
122 << "Committing change to datatype that's not actively enabled."; | |
123 | |
124 string name = meta_entry.Get(syncable::NON_UNIQUE_NAME); | |
125 CHECK(!name.empty()); // Make sure this isn't an update. | |
126 TruncateUTF8ToByteSize(name, 255, &name); | |
127 sync_entry->set_name(name); | |
128 | |
129 // Set the non_unique_name. If we do, the server ignores | |
130 // the |name| value (using |non_unique_name| instead), and will return | |
131 // in the CommitResponse a unique name if one is generated. | |
132 // We send both because it may aid in logging. | |
133 sync_entry->set_non_unique_name(name); | |
134 | |
135 if (!meta_entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { | |
136 sync_entry->set_client_defined_unique_tag( | |
137 meta_entry.Get(syncable::UNIQUE_CLIENT_TAG)); | |
138 } | |
139 | |
140 // Deleted items with server-unknown parent ids can be a problem so we set | |
141 // the parent to 0. (TODO(sync): Still true in protocol?). | |
142 Id new_parent_id; | |
143 if (meta_entry.Get(syncable::IS_DEL) && | |
144 !meta_entry.Get(syncable::PARENT_ID).ServerKnows()) { | |
145 new_parent_id = session->write_transaction()->root_id(); | |
146 } else { | |
147 new_parent_id = meta_entry.Get(syncable::PARENT_ID); | |
148 } | |
149 sync_entry->set_parent_id(new_parent_id); | |
150 | |
151 // If our parent has changed, send up the old one so the server | |
152 // can correctly deal with multiple parents. | |
153 // TODO(nick): With the server keeping track of the primary sync parent, | |
154 // it should not be necessary to provide the old_parent_id: the version | |
155 // number should suffice. | |
156 if (new_parent_id != meta_entry.Get(syncable::SERVER_PARENT_ID) && | |
157 0 != meta_entry.Get(syncable::BASE_VERSION) && | |
158 syncable::CHANGES_VERSION != meta_entry.Get(syncable::BASE_VERSION)) { | |
159 sync_entry->set_old_parent_id(meta_entry.Get(syncable::SERVER_PARENT_ID)); | |
160 } | |
161 | |
162 int64 version = meta_entry.Get(syncable::BASE_VERSION); | |
163 if (syncable::CHANGES_VERSION == version || 0 == version) { | |
164 // Undeletions are only supported for items that have a client tag. | |
165 DCHECK(!id.ServerKnows() || | |
166 !meta_entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) | |
167 << meta_entry; | |
168 | |
169 // Version 0 means to create or undelete an object. | |
170 sync_entry->set_version(0); | |
171 } else { | |
172 DCHECK(id.ServerKnows()) << meta_entry; | |
173 sync_entry->set_version(meta_entry.Get(syncable::BASE_VERSION)); | |
174 } | |
175 sync_entry->set_ctime(TimeToProtoTime(meta_entry.Get(syncable::CTIME))); | |
176 sync_entry->set_mtime(TimeToProtoTime(meta_entry.Get(syncable::MTIME))); | |
177 | |
178 // Deletion is final on the server, let's move things and then delete them. | |
179 if (meta_entry.Get(IS_DEL)) { | |
180 sync_entry->set_deleted(true); | |
181 } else { | |
182 if (meta_entry.Get(SPECIFICS).has_bookmark()) { | |
183 // Common data in both new and old protocol. | |
184 const Id& prev_id = meta_entry.Get(syncable::PREV_ID); | |
185 string prev_id_string = | |
186 prev_id.IsRoot() ? string() : prev_id.GetServerId(); | |
187 sync_entry->set_insert_after_item_id(prev_id_string); | |
188 | |
189 // Compute a numeric position based on what we know locally. | |
190 std::pair<int64, int64> position_block( | |
191 GetFirstPosition(), GetLastPosition()); | |
192 std::map<Id, std::pair<int64, int64> >::iterator prev_pos = | |
193 position_map.find(prev_id); | |
194 if (prev_pos != position_map.end()) { | |
195 position_block = prev_pos->second; | |
196 position_map.erase(prev_pos); | |
197 } else { | |
198 position_block = std::make_pair( | |
199 FindAnchorPosition(syncable::PREV_ID, meta_entry), | |
200 FindAnchorPosition(syncable::NEXT_ID, meta_entry)); | |
201 } | |
202 position_block.first = InterpolatePosition(position_block.first, | |
203 position_block.second); | |
204 | |
205 position_map[id] = position_block; | |
206 sync_entry->set_position_in_parent(position_block.first); | |
207 } | |
208 SetEntrySpecifics(&meta_entry, sync_entry); | |
209 } | |
210 } | |
211 session->mutable_status_controller()-> | |
212 mutable_commit_message()->CopyFrom(message); | |
213 | |
214 return SYNCER_OK; | |
215 } | |
216 | |
217 int64 BuildCommitCommand::FindAnchorPosition(syncable::IdField direction, | |
218 const syncable::Entry& entry) { | |
219 Id next_id = entry.Get(direction); | |
220 while (!next_id.IsRoot()) { | |
221 Entry next_entry(entry.trans(), | |
222 syncable::GET_BY_ID, | |
223 next_id); | |
224 if (!next_entry.Get(IS_UNSYNCED) && !next_entry.Get(IS_UNAPPLIED_UPDATE)) { | |
225 return next_entry.Get(SERVER_POSITION_IN_PARENT); | |
226 } | |
227 next_id = next_entry.Get(direction); | |
228 } | |
229 return | |
230 direction == syncable::PREV_ID ? | |
231 GetFirstPosition() : GetLastPosition(); | |
232 } | |
233 | |
234 int64 BuildCommitCommand::InterpolatePosition(const int64 lo, | |
235 const int64 hi) { | |
236 DCHECK_LE(lo, hi); | |
237 | |
238 // The first item to be added under a parent gets a position of zero. | |
239 if (lo == GetFirstPosition() && hi == GetLastPosition()) | |
240 return 0; | |
241 | |
242 // For small gaps, we do linear interpolation. For larger gaps, | |
243 // we use an additive offset of |GetGap()|. We are careful to avoid | |
244 // signed integer overflow. | |
245 uint64 delta = static_cast<uint64>(hi) - static_cast<uint64>(lo); | |
246 if (delta <= static_cast<uint64>(GetGap()*2)) | |
247 return lo + (static_cast<int64>(delta) + 7) / 8; // Interpolate. | |
248 else if (lo == GetFirstPosition()) | |
249 return hi - GetGap(); // Extend range just before successor. | |
250 else | |
251 return lo + GetGap(); // Use or extend range just after predecessor. | |
252 } | |
253 | |
254 | |
255 } // namespace browser_sync | |
OLD | NEW |