OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/sync/engine/net/server_connection_manager.h" | 5 #include "chrome/browser/sync/engine/net/server_connection_manager.h" |
6 | 6 |
7 #include <errno.h> | 7 #include <errno.h> |
8 | 8 |
9 #include <ostream> | 9 #include <ostream> |
10 #include <string> | 10 #include <string> |
11 #include <vector> | 11 #include <vector> |
12 | 12 |
13 #include "base/auto_reset.h" | |
13 #include "base/command_line.h" | 14 #include "base/command_line.h" |
14 #include "build/build_config.h" | 15 #include "build/build_config.h" |
15 #include "chrome/browser/sync/engine/net/url_translator.h" | 16 #include "chrome/browser/sync/engine/net/url_translator.h" |
16 #include "chrome/browser/sync/engine/syncer.h" | 17 #include "chrome/browser/sync/engine/syncer.h" |
17 #include "chrome/browser/sync/engine/syncproto.h" | 18 #include "chrome/browser/sync/engine/syncproto.h" |
18 #include "chrome/browser/sync/protocol/sync.pb.h" | 19 #include "chrome/browser/sync/protocol/sync.pb.h" |
19 #include "chrome/browser/sync/syncable/directory_manager.h" | 20 #include "chrome/browser/sync/syncable/directory_manager.h" |
20 #include "chrome/common/net/http_return.h" | 21 #include "chrome/common/net/http_return.h" |
21 #include "googleurl/src/gurl.h" | 22 #include "googleurl/src/gurl.h" |
22 | 23 |
(...skipping 28 matching lines...) Expand all Loading... | |
51 ENUM_CASE(SYNC_AUTH_ERROR); | 52 ENUM_CASE(SYNC_AUTH_ERROR); |
52 ENUM_CASE(SERVER_CONNECTION_OK); | 53 ENUM_CASE(SERVER_CONNECTION_OK); |
53 ENUM_CASE(RETRY); | 54 ENUM_CASE(RETRY); |
54 } | 55 } |
55 NOTREACHED(); | 56 NOTREACHED(); |
56 return ""; | 57 return ""; |
57 } | 58 } |
58 | 59 |
59 #undef ENUM_CASE | 60 #undef ENUM_CASE |
60 | 61 |
61 bool ServerConnectionManager::Post::ReadBufferResponse( | 62 ServerConnectionManager::Connection::Connection( |
63 ServerConnectionManager* scm) : scm_(scm), timing_info_(0) { | |
64 } | |
65 | |
66 ServerConnectionManager::Connection::~Connection() { | |
67 scm_->OnConnectionDestroyed(this); | |
68 } | |
69 | |
70 bool ServerConnectionManager::Connection::ReadBufferResponse( | |
62 string* buffer_out, | 71 string* buffer_out, |
63 HttpResponse* response, | 72 HttpResponse* response, |
64 bool require_response) { | 73 bool require_response) { |
65 if (RC_REQUEST_OK != response->response_code) { | 74 if (RC_REQUEST_OK != response->response_code) { |
66 response->server_status = HttpResponse::SYNC_SERVER_ERROR; | 75 response->server_status = HttpResponse::SYNC_SERVER_ERROR; |
67 return false; | 76 return false; |
68 } | 77 } |
69 | 78 |
70 if (require_response && (1 > response->content_length)) | 79 if (require_response && (1 > response->content_length)) |
71 return false; | 80 return false; |
72 | 81 |
73 const int64 bytes_read = ReadResponse(buffer_out, | 82 const int64 bytes_read = ReadResponse(buffer_out, |
74 static_cast<int>(response->content_length)); | 83 static_cast<int>(response->content_length)); |
75 if (bytes_read != response->content_length) { | 84 if (bytes_read != response->content_length) { |
76 response->server_status = HttpResponse::IO_ERROR; | 85 response->server_status = HttpResponse::IO_ERROR; |
77 return false; | 86 return false; |
78 } | 87 } |
79 return true; | 88 return true; |
80 } | 89 } |
81 | 90 |
82 bool ServerConnectionManager::Post::ReadDownloadResponse( | 91 bool ServerConnectionManager::Connection::ReadDownloadResponse( |
83 HttpResponse* response, | 92 HttpResponse* response, |
84 string* buffer_out) { | 93 string* buffer_out) { |
85 const int64 bytes_read = ReadResponse(buffer_out, | 94 const int64 bytes_read = ReadResponse(buffer_out, |
86 static_cast<int>(response->content_length)); | 95 static_cast<int>(response->content_length)); |
87 | 96 |
88 if (bytes_read != response->content_length) { | 97 if (bytes_read != response->content_length) { |
89 LOG(ERROR) << "Mismatched content lengths, server claimed " << | 98 LOG(ERROR) << "Mismatched content lengths, server claimed " << |
90 response->content_length << ", but sent " << bytes_read; | 99 response->content_length << ", but sent " << bytes_read; |
91 response->server_status = HttpResponse::IO_ERROR; | 100 response->server_status = HttpResponse::IO_ERROR; |
92 return false; | 101 return false; |
93 } | 102 } |
94 return true; | 103 return true; |
95 } | 104 } |
96 | 105 |
97 namespace { | 106 namespace { |
98 | 107 |
99 string StripTrailingSlash(const string& s) { | 108 string StripTrailingSlash(const string& s) { |
100 int stripped_end_pos = s.size(); | 109 int stripped_end_pos = s.size(); |
101 if (s.at(stripped_end_pos - 1) == '/') { | 110 if (s.at(stripped_end_pos - 1) == '/') { |
102 stripped_end_pos = stripped_end_pos - 1; | 111 stripped_end_pos = stripped_end_pos - 1; |
103 } | 112 } |
104 | 113 |
105 return s.substr(0, stripped_end_pos); | 114 return s.substr(0, stripped_end_pos); |
106 } | 115 } |
107 | 116 |
108 } // namespace | 117 } // namespace |
109 | 118 |
110 // TODO(chron): Use a GURL instead of string concatenation. | 119 // TODO(chron): Use a GURL instead of string concatenation. |
111 string ServerConnectionManager::Post::MakeConnectionURL( | 120 string ServerConnectionManager::Connection::MakeConnectionURL( |
112 const string& sync_server, | 121 const string& sync_server, |
113 const string& path, | 122 const string& path, |
114 bool use_ssl) const { | 123 bool use_ssl) const { |
115 string connection_url = (use_ssl ? "https://" : "http://"); | 124 string connection_url = (use_ssl ? "https://" : "http://"); |
116 connection_url += sync_server; | 125 connection_url += sync_server; |
117 connection_url = StripTrailingSlash(connection_url); | 126 connection_url = StripTrailingSlash(connection_url); |
118 connection_url += path; | 127 connection_url += path; |
119 | 128 |
120 return connection_url; | 129 return connection_url; |
121 } | 130 } |
122 | 131 |
123 int ServerConnectionManager::Post::ReadResponse(string* out_buffer, | 132 int ServerConnectionManager::Connection::ReadResponse(string* out_buffer, |
124 int length) { | 133 int length) { |
125 int bytes_read = buffer_.length(); | 134 int bytes_read = buffer_.length(); |
126 CHECK(length <= bytes_read); | 135 CHECK(length <= bytes_read); |
127 out_buffer->assign(buffer_); | 136 out_buffer->assign(buffer_); |
128 return bytes_read; | 137 return bytes_read; |
129 } | 138 } |
130 | 139 |
131 ScopedServerStatusWatcher::ScopedServerStatusWatcher( | 140 ScopedServerStatusWatcher::ScopedServerStatusWatcher( |
132 ServerConnectionManager* conn_mgr, HttpResponse* response) | 141 ServerConnectionManager* conn_mgr, HttpResponse* response) |
133 : conn_mgr_(conn_mgr), | 142 : conn_mgr_(conn_mgr), |
134 response_(response), | 143 response_(response), |
(...skipping 18 matching lines...) Expand all Loading... | |
153 bool use_ssl, | 162 bool use_ssl, |
154 const string& user_agent) | 163 const string& user_agent) |
155 : sync_server_(server), | 164 : sync_server_(server), |
156 sync_server_port_(port), | 165 sync_server_port_(port), |
157 user_agent_(user_agent), | 166 user_agent_(user_agent), |
158 use_ssl_(use_ssl), | 167 use_ssl_(use_ssl), |
159 proto_sync_path_(kSyncServerSyncPath), | 168 proto_sync_path_(kSyncServerSyncPath), |
160 get_time_path_(kSyncServerGetTimePath), | 169 get_time_path_(kSyncServerGetTimePath), |
161 error_count_(0), | 170 error_count_(0), |
162 server_status_(HttpResponse::NONE), | 171 server_status_(HttpResponse::NONE), |
163 server_reachable_(false) { | 172 server_reachable_(false), |
173 terminated_(false), | |
174 active_connection_(false) { | |
164 } | 175 } |
165 | 176 |
166 ServerConnectionManager::~ServerConnectionManager() { | 177 ServerConnectionManager::~ServerConnectionManager() { |
167 } | 178 } |
168 | 179 |
180 ServerConnectionManager::Connection* | |
181 ServerConnectionManager::MakeActiveConnection() { | |
182 base::AutoLock lock(terminate_connection_lock_); | |
183 DCHECK(!active_connection_); | |
184 if (terminated_) | |
185 return NULL; | |
186 | |
187 active_connection_ = MakeConnection(); | |
188 return active_connection_; | |
189 } | |
190 | |
191 void ServerConnectionManager::OnConnectionDestroyed(Connection* connection) { | |
192 DCHECK(connection); | |
193 base::AutoLock lock(terminate_connection_lock_); | |
194 if (active_connection_ != connection) | |
akalin
2011/08/31 21:10:03
can't this be DCHECK_EQ(active_connection_, connec
tim (not reviewing)
2011/08/31 21:32:10
active_connection_ can be NULL already if it was a
| |
195 return; | |
196 | |
197 active_connection_ = NULL; | |
198 } | |
199 | |
169 void ServerConnectionManager::NotifyStatusChanged() { | 200 void ServerConnectionManager::NotifyStatusChanged() { |
170 DCHECK(CalledOnValidThread()); | 201 DCHECK(thread_checker_.CalledOnValidThread()); |
171 FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_, | 202 FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_, |
172 OnServerConnectionEvent( | 203 OnServerConnectionEvent( |
173 ServerConnectionEvent(server_status_, server_reachable_))); | 204 ServerConnectionEvent(server_status_, server_reachable_))); |
174 } | 205 } |
175 | 206 |
176 bool ServerConnectionManager::PostBufferWithCachedAuth( | 207 bool ServerConnectionManager::PostBufferWithCachedAuth( |
177 const PostBufferParams* params, ScopedServerStatusWatcher* watcher) { | 208 const PostBufferParams* params, ScopedServerStatusWatcher* watcher) { |
178 DCHECK(CalledOnValidThread()); | 209 DCHECK(thread_checker_.CalledOnValidThread()); |
179 string path = | 210 string path = |
180 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); | 211 MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_)); |
181 return PostBufferToPath(params, path, auth_token(), watcher); | 212 return PostBufferToPath(params, path, auth_token(), watcher); |
182 } | 213 } |
183 | 214 |
184 bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params, | 215 bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params, |
185 const string& path, const string& auth_token, | 216 const string& path, const string& auth_token, |
186 ScopedServerStatusWatcher* watcher) { | 217 ScopedServerStatusWatcher* watcher) { |
187 DCHECK(CalledOnValidThread()); | 218 DCHECK(thread_checker_.CalledOnValidThread()); |
188 DCHECK(watcher != NULL); | 219 DCHECK(watcher != NULL); |
189 | 220 |
190 if (auth_token.empty()) { | 221 if (auth_token.empty()) { |
191 params->response->server_status = HttpResponse::SYNC_AUTH_ERROR; | 222 params->response->server_status = HttpResponse::SYNC_AUTH_ERROR; |
192 return false; | 223 return false; |
193 } | 224 } |
194 | 225 |
195 scoped_ptr<Post> post(MakePost()); | 226 // When our connection object falls out of scope, it clears itself from |
227 // active_connection_. | |
228 scoped_ptr<Connection> post(MakeActiveConnection()); | |
229 if (!post.get()) { | |
230 params->response->server_status = HttpResponse::CONNECTION_UNAVAILABLE; | |
231 return false; | |
232 } | |
233 | |
196 post->set_timing_info(params->timing_info); | 234 post->set_timing_info(params->timing_info); |
197 bool ok = post->Init(path.c_str(), auth_token, params->buffer_in, | 235 // Note that |post| may be aborted by now, which will just cause Init to fail |
198 params->response); | 236 // with CONNECTION_UNAVAILABLE. |
237 bool ok = post->Init( | |
238 path.c_str(), auth_token, params->buffer_in, params->response); | |
199 | 239 |
200 if (params->response->server_status == HttpResponse::SYNC_AUTH_ERROR) { | 240 if (params->response->server_status == HttpResponse::SYNC_AUTH_ERROR) { |
201 InvalidateAndClearAuthToken(); | 241 InvalidateAndClearAuthToken(); |
202 } | 242 } |
203 | 243 |
204 if (!ok || RC_REQUEST_OK != params->response->response_code) { | 244 if (!ok || RC_REQUEST_OK != params->response->response_code) { |
205 IncrementErrorCount(); | 245 IncrementErrorCount(); |
206 return false; | 246 return false; |
207 } | 247 } |
208 | 248 |
209 if (post->ReadBufferResponse(params->buffer_out, params->response, true)) { | 249 if (post->ReadBufferResponse(params->buffer_out, params->response, true)) { |
210 params->response->server_status = HttpResponse::SERVER_CONNECTION_OK; | 250 params->response->server_status = HttpResponse::SERVER_CONNECTION_OK; |
211 server_reachable_ = true; | 251 server_reachable_ = true; |
212 return true; | 252 return true; |
213 } | 253 } |
214 return false; | 254 return false; |
215 } | 255 } |
216 | 256 |
217 bool ServerConnectionManager::CheckTime(int32* out_time) { | 257 bool ServerConnectionManager::CheckTime(int32* out_time) { |
218 DCHECK(CalledOnValidThread()); | 258 DCHECK(thread_checker_.CalledOnValidThread()); |
259 | |
219 // Verify that the server really is reachable by checking the time. We need | 260 // Verify that the server really is reachable by checking the time. We need |
220 // to do this because of wifi interstitials that intercept messages from the | 261 // to do this because of wifi interstitials that intercept messages from the |
221 // client and return HTTP OK instead of a redirect. | 262 // client and return HTTP OK instead of a redirect. |
222 HttpResponse response; | 263 HttpResponse response; |
223 ScopedServerStatusWatcher watcher(this, &response); | 264 ScopedServerStatusWatcher watcher(this, &response); |
224 string post_body = "command=get_time"; | 265 string post_body = "command=get_time"; |
225 | 266 |
226 for (int i = 0 ; i < 3; i++) { | 267 for (int i = 0 ; i < 3; i++) { |
227 scoped_ptr<Post> post(MakePost()); | 268 scoped_ptr<Connection> post(MakeActiveConnection()); |
269 if (!post.get()) | |
270 break; | |
228 | 271 |
229 // Note that the server's get_time path doesn't require authentication. | 272 // Note that the server's get_time path doesn't require authentication. |
230 string get_time_path = | 273 string get_time_path = |
231 MakeSyncServerPath(kSyncServerGetTimePath, post_body); | 274 MakeSyncServerPath(kSyncServerGetTimePath, post_body); |
232 VLOG(1) << "Requesting get_time from:" << get_time_path; | 275 VLOG(1) << "Requesting get_time from:" << get_time_path; |
233 | 276 |
234 string blank_post_body; | 277 string blank_post_body; |
235 bool ok = post->Init(get_time_path.c_str(), blank_post_body, | 278 bool ok = post->Init(get_time_path.c_str(), blank_post_body, |
236 blank_post_body, &response); | 279 blank_post_body, &response); |
237 if (!ok) { | 280 if (!ok) { |
(...skipping 12 matching lines...) Expand all Loading... | |
250 } | 293 } |
251 *out_time = atoi(time_response.c_str()); | 294 *out_time = atoi(time_response.c_str()); |
252 VLOG(1) << "Server was reachable."; | 295 VLOG(1) << "Server was reachable."; |
253 return true; | 296 return true; |
254 } | 297 } |
255 IncrementErrorCount(); | 298 IncrementErrorCount(); |
256 return false; | 299 return false; |
257 } | 300 } |
258 | 301 |
259 bool ServerConnectionManager::IsServerReachable() { | 302 bool ServerConnectionManager::IsServerReachable() { |
260 DCHECK(CalledOnValidThread()); | 303 DCHECK(thread_checker_.CalledOnValidThread()); |
261 int32 time; | 304 int32 time; |
262 return CheckTime(&time); | 305 return CheckTime(&time); |
263 } | 306 } |
264 | 307 |
265 bool ServerConnectionManager::IsUserAuthenticated() { | 308 bool ServerConnectionManager::IsUserAuthenticated() { |
266 DCHECK(CalledOnValidThread()); | 309 DCHECK(thread_checker_.CalledOnValidThread()); |
267 return IsGoodReplyFromServer(server_status_); | 310 return IsGoodReplyFromServer(server_status_); |
268 } | 311 } |
269 | 312 |
270 bool ServerConnectionManager::CheckServerReachable() { | 313 bool ServerConnectionManager::CheckServerReachable() { |
271 DCHECK(CalledOnValidThread()); | 314 DCHECK(thread_checker_.CalledOnValidThread()); |
272 const bool server_is_reachable = IsServerReachable(); | 315 const bool server_is_reachable = IsServerReachable(); |
273 if (server_reachable_ != server_is_reachable) { | 316 if (server_reachable_ != server_is_reachable) { |
274 server_reachable_ = server_is_reachable; | 317 server_reachable_ = server_is_reachable; |
275 NotifyStatusChanged(); | 318 NotifyStatusChanged(); |
276 } | 319 } |
277 return server_is_reachable; | 320 return server_is_reachable; |
278 } | 321 } |
279 | 322 |
280 bool ServerConnectionManager::IncrementErrorCount() { | 323 bool ServerConnectionManager::IncrementErrorCount() { |
281 DCHECK(CalledOnValidThread()); | 324 DCHECK(thread_checker_.CalledOnValidThread()); |
282 error_count_++; | 325 error_count_++; |
283 | 326 |
284 if (error_count_ > kMaxConnectionErrorsBeforeReset) { | 327 if (error_count_ > kMaxConnectionErrorsBeforeReset) { |
285 error_count_ = 0; | 328 error_count_ = 0; |
286 | 329 |
287 if (!IsServerReachable()) { | 330 if (!IsServerReachable()) { |
288 LOG(WARNING) << "Too many connection failures, server is not reachable. " | 331 LOG(WARNING) << "Too many connection failures, server is not reachable. " |
289 << "Resetting connections."; | 332 << "Resetting connections."; |
290 } else { | 333 } else { |
291 LOG(WARNING) << "Multiple connection failures while server is reachable."; | 334 LOG(WARNING) << "Multiple connection failures while server is reachable."; |
292 } | 335 } |
293 return false; | 336 return false; |
294 } | 337 } |
295 | 338 |
296 return true; | 339 return true; |
297 } | 340 } |
298 | 341 |
299 void ServerConnectionManager::SetServerParameters(const string& server_url, | 342 void ServerConnectionManager::SetServerParameters(const string& server_url, |
300 int port, | 343 int port, |
301 bool use_ssl) { | 344 bool use_ssl) { |
302 DCHECK(CalledOnValidThread()); | 345 DCHECK(thread_checker_.CalledOnValidThread()); |
303 sync_server_ = server_url; | 346 sync_server_ = server_url; |
304 sync_server_port_ = port; | 347 sync_server_port_ = port; |
305 use_ssl_ = use_ssl; | 348 use_ssl_ = use_ssl; |
306 } | 349 } |
307 | 350 |
308 // Returns the current server parameters in server_url and port. | 351 // Returns the current server parameters in server_url and port. |
309 void ServerConnectionManager::GetServerParameters(string* server_url, | 352 void ServerConnectionManager::GetServerParameters(string* server_url, |
310 int* port, | 353 int* port, |
311 bool* use_ssl) const { | 354 bool* use_ssl) const { |
312 if (server_url != NULL) | 355 if (server_url != NULL) |
(...skipping 14 matching lines...) Expand all Loading... | |
327 return std::string(); | 370 return std::string(); |
328 // We just want the hostname, so we don't need to switch on use_ssl. | 371 // We just want the hostname, so we don't need to switch on use_ssl. |
329 server_url = "http://" + server_url; | 372 server_url = "http://" + server_url; |
330 GURL gurl(server_url); | 373 GURL gurl(server_url); |
331 DCHECK(gurl.is_valid()) << gurl; | 374 DCHECK(gurl.is_valid()) << gurl; |
332 return gurl.host(); | 375 return gurl.host(); |
333 } | 376 } |
334 | 377 |
335 void ServerConnectionManager::AddListener( | 378 void ServerConnectionManager::AddListener( |
336 ServerConnectionEventListener* listener) { | 379 ServerConnectionEventListener* listener) { |
337 DCHECK(CalledOnValidThread()); | 380 DCHECK(thread_checker_.CalledOnValidThread()); |
338 listeners_.AddObserver(listener); | 381 listeners_.AddObserver(listener); |
339 } | 382 } |
340 | 383 |
341 void ServerConnectionManager::RemoveListener( | 384 void ServerConnectionManager::RemoveListener( |
342 ServerConnectionEventListener* listener) { | 385 ServerConnectionEventListener* listener) { |
343 DCHECK(CalledOnValidThread()); | 386 DCHECK(thread_checker_.CalledOnValidThread()); |
344 listeners_.RemoveObserver(listener); | 387 listeners_.RemoveObserver(listener); |
345 } | 388 } |
346 | 389 |
347 ServerConnectionManager::Post* ServerConnectionManager::MakePost() { | 390 ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection() |
391 { | |
348 return NULL; // For testing. | 392 return NULL; // For testing. |
349 } | 393 } |
350 | 394 |
395 void ServerConnectionManager::TerminateAllIO() { | |
396 base::AutoLock lock(terminate_connection_lock_); | |
397 terminated_ = true; | |
398 if (active_connection_) | |
399 active_connection_->Abort(); | |
400 | |
401 // Sever our ties to this connection object. Note that it still may exist, | |
402 // since we don't own it, but it has been neutered. | |
403 active_connection_ = NULL; | |
404 } | |
405 | |
351 bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm, | 406 bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm, |
352 syncable::DirectoryManager* manager, | 407 syncable::DirectoryManager* manager, |
353 const std::string& share) { | 408 const std::string& share) { |
354 syncable::ScopedDirLookup dir(manager, share); | 409 syncable::ScopedDirLookup dir(manager, share); |
355 if (!dir.good()) { | 410 if (!dir.good()) { |
356 VLOG(1) << "Dir lookup failed"; | 411 VLOG(1) << "Dir lookup failed"; |
357 return false; | 412 return false; |
358 } | 413 } |
359 string birthday = dir->store_birthday(); | 414 string birthday = dir->store_birthday(); |
360 if (!birthday.empty()) | 415 if (!birthday.empty()) |
361 csm->set_store_birthday(birthday); | 416 csm->set_store_birthday(birthday); |
362 csm->set_share(share); | 417 csm->set_share(share); |
363 return true; | 418 return true; |
364 } | 419 } |
365 | 420 |
366 std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) { | 421 std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) { |
367 s << " Response Code (bogus on error): " << hr.response_code; | 422 s << " Response Code (bogus on error): " << hr.response_code; |
368 s << " Content-Length (bogus on error): " << hr.content_length; | 423 s << " Content-Length (bogus on error): " << hr.content_length; |
369 s << " Server Status: " << hr.server_status; | 424 s << " Server Status: " << hr.server_status; |
370 return s; | 425 return s; |
371 } | 426 } |
372 | 427 |
373 } // namespace browser_sync | 428 } // namespace browser_sync |
OLD | NEW |