| Index: chrome/browser/sync/glue/session_model_associator.cc
|
| diff --git a/chrome/browser/sync/glue/session_model_associator.cc b/chrome/browser/sync/glue/session_model_associator.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6242fa7869ddcf535e94df3d2e9fbc714b31f1d4
|
| --- /dev/null
|
| +++ b/chrome/browser/sync/glue/session_model_associator.cc
|
| @@ -0,0 +1,527 @@
|
| +// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/sync/glue/session_model_associator.h"
|
| +
|
| +#include <utility>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/string_util.h"
|
| +#include "base/utf_string_conversions.h"
|
| +#include "chrome/browser/profile.h"
|
| +#include "chrome/browser/sessions/session_id.h"
|
| +#include "chrome/browser/sync/profile_sync_service.h"
|
| +#include "chrome/browser/sync/syncable/syncable.h"
|
| +#include "chrome/common/notification_details.h"
|
| +#include "chrome/common/notification_service.h"
|
| +#include "chrome/common/url_constants.h"
|
| +
|
| +namespace browser_sync {
|
| +
|
| +namespace {
|
| +
|
| +static const char kNoSessionsFolderError[] =
|
| + "Server did not create the top-level sessions node. We "
|
| + "might be running against an out-of-date server.";
|
| +
|
| +} // namespace
|
| +
|
| +SessionModelAssociator::SessionModelAssociator(
|
| + ProfileSyncService* sync_service) : sync_service_(sync_service) {
|
| + DCHECK(CalledOnValidThread());
|
| + DCHECK(sync_service_);
|
| +}
|
| +
|
| +SessionModelAssociator::~SessionModelAssociator() {
|
| + DCHECK(CalledOnValidThread());
|
| +}
|
| +
|
| +// Sends a notification to ForeignSessionHandler to update the UI, because
|
| +// the session corresponding to the id given has changed state.
|
| +void SessionModelAssociator::Associate(
|
| + const sync_pb::SessionSpecifics* specifics, int64 sync_id) {
|
| + DCHECK(CalledOnValidThread());
|
| + NotificationService::current()->Notify(
|
| + NotificationType::FOREIGN_SESSION_UPDATED,
|
| + NotificationService::AllSources(),
|
| + Details<int64>(&sync_id));
|
| +}
|
| +
|
| +bool SessionModelAssociator::AssociateModels() {
|
| + DCHECK(CalledOnValidThread());
|
| + UpdateSyncModelDataFromClient();
|
| + return true;
|
| +}
|
| +
|
| +bool SessionModelAssociator::ChromeModelHasUserCreatedNodes(
|
| + bool* has_nodes) {
|
| + DCHECK(CalledOnValidThread());
|
| + CHECK(has_nodes);
|
| + // This is wrong, but this function is unused, anyway.
|
| + *has_nodes = true;
|
| + return true;
|
| +}
|
| +
|
| +// Sends a notification to ForeignSessionHandler to update the UI, because
|
| +// the session corresponding to the id given has been deleted.
|
| +void SessionModelAssociator::Disassociate(int64 sync_id) {
|
| + NotificationService::current()->Notify(
|
| + NotificationType::FOREIGN_SESSION_DELETED,
|
| + NotificationService::AllSources(),
|
| + Details<int64>(&sync_id));
|
| +}
|
| +
|
| +const sync_pb::SessionSpecifics* SessionModelAssociator::
|
| + GetChromeNodeFromSyncId(int64 sync_id) {
|
| + sync_api::ReadTransaction trans(
|
| + sync_service_->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode node(&trans);
|
| + if (!node.InitByIdLookup(sync_id))
|
| + return NULL;
|
| + return new sync_pb::SessionSpecifics(node.GetSessionSpecifics());
|
| +}
|
| +
|
| +bool SessionModelAssociator::GetSyncIdForTaggedNode(const std::string* tag,
|
| + int64* sync_id) {
|
| + sync_api::ReadTransaction trans(
|
| + sync_service_->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode node(&trans);
|
| + if (!node.InitByClientTagLookup(syncable::SESSIONS, *tag))
|
| + return false;
|
| + *sync_id = node.GetId();
|
| + return true;
|
| +}
|
| +
|
| +int64 SessionModelAssociator::GetSyncIdFromChromeId(std::string id) {
|
| + sync_api::ReadTransaction trans(
|
| + sync_service_->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode node(&trans);
|
| + if (!node.InitByClientTagLookup(syncable::SESSIONS, id))
|
| + return sync_api::kInvalidId;
|
| + return node.GetId();
|
| +}
|
| +
|
| +bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
|
| + DCHECK(CalledOnValidThread());
|
| + CHECK(has_nodes);
|
| + *has_nodes = false;
|
| + sync_api::ReadTransaction trans(
|
| + sync_service_->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode root(&trans);
|
| + if (!root.InitByTagLookup(kSessionsTag)) {
|
| + LOG(ERROR) << kNoSessionsFolderError;
|
| + return false;
|
| + }
|
| + // The sync model has user created nodes iff the sessions folder has
|
| + // any children.
|
| + *has_nodes = root.GetFirstChildId() != sync_api::kInvalidId;
|
| + return true;
|
| +}
|
| +
|
| +std::string SessionModelAssociator::GetCurrentMachineTag() {
|
| + if (current_machine_tag_.empty())
|
| + InitializeCurrentMachineTag();
|
| + DCHECK(!current_machine_tag_.empty());
|
| + return current_machine_tag_;
|
| +}
|
| +
|
| +void SessionModelAssociator::AppendForeignSessionFromSpecifics(
|
| + const sync_pb::SessionSpecifics* specifics,
|
| + std::vector<ForeignSession*>* session) {
|
| + ForeignSession* foreign_session = new ForeignSession();
|
| + foreign_session->foreign_tession_tag = specifics->session_tag();
|
| + session->insert(session->end(), foreign_session);
|
| + for (int i = 0; i < specifics->session_window_size(); i++) {
|
| + const sync_pb::SessionWindow* window = &specifics->session_window(i);
|
| + SessionWindow* session_window = new SessionWindow();
|
| + PopulateSessionWindowFromSpecifics(session_window, window);
|
| + foreign_session->windows.insert(
|
| + foreign_session->windows.end(), session_window);
|
| + }
|
| +}
|
| +
|
| +// Fills the given vector with foreign session windows to restore.
|
| +void SessionModelAssociator::AppendForeignSessionWithID(int64 id,
|
| + std::vector<ForeignSession*>* session, sync_api::BaseTransaction* trans) {
|
| + if (id == sync_api::kInvalidId)
|
| + return;
|
| + sync_api::ReadNode node(trans);
|
| + if (!node.InitByIdLookup(id))
|
| + return;
|
| + const sync_pb::SessionSpecifics* ref = &node.GetSessionSpecifics();
|
| + AppendForeignSessionFromSpecifics(ref, session);
|
| +}
|
| +
|
| +void SessionModelAssociator::UpdateSyncModelDataFromClient() {
|
| + DCHECK(CalledOnValidThread());
|
| + SessionService::SessionCallback* callback =
|
| + NewCallback(this, &SessionModelAssociator::OnGotSession);
|
| + // TODO(jerrica): Stop current race condition, possibly make new method in
|
| + // session service, which only grabs the windows from memory.
|
| + GetSessionService()->GetCurrentSession(&consumer_, callback);
|
| +}
|
| +
|
| +bool SessionModelAssociator::GetSessionDataFromSyncModel(
|
| + std::vector<ForeignSession*>* sessions) {
|
| + std::vector<const sync_pb::SessionSpecifics*> specifics;
|
| + DCHECK(CalledOnValidThread());
|
| + sync_api::ReadTransaction trans(
|
| + sync_service_->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode root(&trans);
|
| + if (!root.InitByTagLookup(kSessionsTag)) {
|
| + LOG(ERROR) << kNoSessionsFolderError;
|
| + return false;
|
| + }
|
| + sync_api::ReadNode current_machine(&trans);
|
| + int64 current_id = (current_machine.InitByClientTagLookup(syncable::SESSIONS,
|
| + GetCurrentMachineTag())) ? current_machine.GetId() : sync_api::kInvalidId;
|
| + // Iterate through the nodes and populate the session model.
|
| + int64 id = root.GetFirstChildId();
|
| + while (id != sync_api::kInvalidId) {
|
| + sync_api::ReadNode sync_node(&trans);
|
| + if (!sync_node.InitByIdLookup(id)) {
|
| + LOG(ERROR) << "Failed to fetch sync node for id " << id;
|
| + return false;
|
| + }
|
| + if (id != current_id) {
|
| + specifics.insert(specifics.end(), &sync_node.GetSessionSpecifics());
|
| + }
|
| + id = sync_node.GetSuccessorId();
|
| + }
|
| + for (std::vector<const sync_pb::SessionSpecifics*>::const_iterator i =
|
| + specifics.begin(); i != specifics.end(); ++i) {
|
| + AppendForeignSessionFromSpecifics(*i, sessions);
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +SessionService* SessionModelAssociator::GetSessionService() {
|
| + DCHECK(sync_service_);
|
| + Profile* profile = sync_service_->profile();
|
| + DCHECK(profile);
|
| + SessionService* sessions_service = profile->GetSessionService();
|
| + DCHECK(sessions_service);
|
| + return sessions_service;
|
| +}
|
| +
|
| +void SessionModelAssociator::InitializeCurrentMachineTag() {
|
| + sync_api::WriteTransaction trans(sync_service_->backend()->
|
| + GetUserShareHandle());
|
| + syncable::Directory* dir =
|
| + trans.GetWrappedWriteTrans()->directory();
|
| + current_machine_tag_ = "session_sync" + dir->cache_guid();
|
| +}
|
| +
|
| +// See PopulateSessionSpecificsTab for use. May add functionality that includes
|
| +// the state later.
|
| +void SessionModelAssociator::PopulateSessionSpecificsNavigation(
|
| + const TabNavigation* navigation, sync_pb::TabNavigation* tab_navigation) {
|
| + tab_navigation->set_index(navigation->index());
|
| + tab_navigation->set_virtual_url(navigation->virtual_url().spec());
|
| + tab_navigation->set_referrer(navigation->referrer().spec());
|
| + tab_navigation->set_title(UTF16ToUTF8(navigation->title()));
|
| + switch (navigation->transition()) {
|
| + case PageTransition::LINK:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_LINK);
|
| + break;
|
| + case PageTransition::TYPED:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_TYPED);
|
| + break;
|
| + case PageTransition::AUTO_BOOKMARK:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK);
|
| + break;
|
| + case PageTransition::AUTO_SUBFRAME:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME);
|
| + break;
|
| + case PageTransition::MANUAL_SUBFRAME:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME);
|
| + break;
|
| + case PageTransition::GENERATED:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_GENERATED);
|
| + break;
|
| + case PageTransition::START_PAGE:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_START_PAGE);
|
| + break;
|
| + case PageTransition::FORM_SUBMIT:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_FORM_SUBMIT);
|
| + break;
|
| + case PageTransition::RELOAD:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_RELOAD);
|
| + break;
|
| + case PageTransition::KEYWORD:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_KEYWORD);
|
| + break;
|
| + case PageTransition::KEYWORD_GENERATED:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED);
|
| + break;
|
| + case PageTransition::CHAIN_START:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_CHAIN_START);
|
| + break;
|
| + case PageTransition::CHAIN_END:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_CHAIN_END);
|
| + break;
|
| + case PageTransition::CLIENT_REDIRECT:
|
| + tab_navigation->set_navigation_qualifier(
|
| + sync_pb::TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT);
|
| + break;
|
| + case PageTransition::SERVER_REDIRECT:
|
| + tab_navigation->set_navigation_qualifier(
|
| + sync_pb::TabNavigation_PageTransitionQualifier_SERVER_REDIRECT);
|
| + break;
|
| + default:
|
| + tab_navigation->set_page_transition(
|
| + sync_pb::TabNavigation_PageTransition_TYPED);
|
| + }
|
| +}
|
| +
|
| +// See PopulateSessionSpecificsWindow for use.
|
| +void SessionModelAssociator::PopulateSessionSpecificsTab(
|
| + const SessionTab* tab, sync_pb::SessionTab* session_tab) {
|
| + session_tab->set_tab_visual_index(tab->tab_visual_index);
|
| + session_tab->set_current_navigation_index(
|
| + tab->current_navigation_index);
|
| + session_tab->set_pinned(tab->pinned);
|
| + session_tab->set_extension_app_id(tab->extension_app_id);
|
| + for (std::vector<TabNavigation>::const_iterator i3 =
|
| + tab->navigations.begin(); i3 != tab->navigations.end(); ++i3) {
|
| + const TabNavigation navigation = *i3;
|
| + sync_pb::TabNavigation* tab_navigation =
|
| + session_tab->add_navigation();
|
| + PopulateSessionSpecificsNavigation(&navigation, tab_navigation);
|
| + }
|
| +}
|
| +
|
| +// Called when populating session specifics to send to the sync model, called
|
| +// when associating models, or updating the sync model.
|
| +void SessionModelAssociator::PopulateSessionSpecificsWindow(
|
| + const SessionWindow* window, sync_pb::SessionWindow* session_window) {
|
| + session_window->set_selected_tab_index(window->selected_tab_index);
|
| + if (window->type == 1) {
|
| + session_window->set_browser_type(
|
| + sync_pb::SessionWindow_BrowserType_TYPE_NORMAL);
|
| + } else {
|
| + session_window->set_browser_type(
|
| + sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
|
| + }
|
| + for (std::vector<SessionTab*>::const_iterator i2 = window->tabs.begin();
|
| + i2 != window->tabs.end(); ++i2) {
|
| + const SessionTab* tab = *i2;
|
| + if (tab->navigations.at(tab->current_navigation_index).virtual_url() ==
|
| + GURL(chrome::kChromeUINewTabURL)) {
|
| + continue;
|
| + }
|
| + sync_pb::SessionTab* session_tab = session_window->add_session_tab();
|
| + PopulateSessionSpecificsTab(tab, session_tab);
|
| + }
|
| +}
|
| +
|
| +bool SessionModelAssociator::WindowHasNoTabsToSync(
|
| + const SessionWindow* window) {
|
| + int num_populated = 0;
|
| + for (std::vector<SessionTab*>::const_iterator i = window->tabs.begin();
|
| + i != window->tabs.end(); ++i) {
|
| + const SessionTab* tab = *i;
|
| + if (tab->navigations.at(tab->current_navigation_index).virtual_url() ==
|
| + GURL(chrome::kChromeUINewTabURL)) {
|
| + continue;
|
| + }
|
| + num_populated++;
|
| + }
|
| + if (num_populated == 0)
|
| + return true;
|
| + return false;
|
| +}
|
| +
|
| +void SessionModelAssociator::OnGotSession(int handle,
|
| + std::vector<SessionWindow*>* windows) {
|
| + sync_pb::SessionSpecifics session;
|
| + // Set the tag, then iterate through the vector of windows, extracting the
|
| + // window data, along with the tabs data and tab navigation data to populate
|
| + // the session specifics.
|
| + session.set_session_tag(GetCurrentMachineTag());
|
| + for (std::vector<SessionWindow*>::const_iterator i = windows->begin();
|
| + i != windows->end(); ++i) {
|
| + const SessionWindow* window = *i;
|
| + if (WindowHasNoTabsToSync(window)) {
|
| + continue;
|
| + }
|
| + sync_pb::SessionWindow* session_window = session.add_session_window();
|
| + PopulateSessionSpecificsWindow(window, session_window);
|
| + }
|
| + bool has_nodes = false;
|
| + if (!SyncModelHasUserCreatedNodes(&has_nodes))
|
| + return;
|
| + if (session.session_window_size() == 0 && has_nodes)
|
| + return;
|
| + sync_api::WriteTransaction trans(
|
| + sync_service_->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode root(&trans);
|
| + if (!root.InitByTagLookup(kSessionsTag)) {
|
| + LOG(ERROR) << kNoSessionsFolderError;
|
| + return;
|
| + }
|
| + UpdateSyncModel(&session, &trans, &root);
|
| +}
|
| +
|
| +void SessionModelAssociator::AppendSessionTabNavigation(
|
| + std::vector<TabNavigation>* navigations,
|
| + const sync_pb::TabNavigation* navigation) {
|
| + int index = 0;
|
| + GURL virtual_url;
|
| + GURL referrer;
|
| + string16 title;
|
| + std::string state;
|
| + PageTransition::Type transition;
|
| + if (navigation->has_index())
|
| + index = navigation->index();
|
| + if (navigation->has_virtual_url()) {
|
| + GURL gurl(navigation->virtual_url());
|
| + virtual_url = gurl;
|
| + }
|
| + if (navigation->has_referrer()) {
|
| + GURL gurl(navigation->referrer());
|
| + referrer = gurl;
|
| + }
|
| + if (navigation->has_title())
|
| + title = UTF8ToUTF16(navigation->title());
|
| + if (navigation->has_page_transition() ||
|
| + navigation->has_navigation_qualifier()) {
|
| + switch (navigation->page_transition()) {
|
| + case sync_pb::TabNavigation_PageTransition_LINK:
|
| + transition = PageTransition::LINK;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_TYPED:
|
| + transition = PageTransition::TYPED;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_AUTO_BOOKMARK:
|
| + transition = PageTransition::AUTO_BOOKMARK;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_AUTO_SUBFRAME:
|
| + transition = PageTransition::AUTO_SUBFRAME;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_MANUAL_SUBFRAME:
|
| + transition = PageTransition::MANUAL_SUBFRAME;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_GENERATED:
|
| + transition = PageTransition::GENERATED;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_START_PAGE:
|
| + transition = PageTransition::START_PAGE;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_FORM_SUBMIT:
|
| + transition = PageTransition::FORM_SUBMIT;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_RELOAD:
|
| + transition = PageTransition::RELOAD;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_KEYWORD:
|
| + transition = PageTransition::KEYWORD;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_KEYWORD_GENERATED:
|
| + transition = PageTransition::KEYWORD_GENERATED;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_CHAIN_START:
|
| + transition = sync_pb::TabNavigation_PageTransition_CHAIN_START;
|
| + break;
|
| + case sync_pb::TabNavigation_PageTransition_CHAIN_END:
|
| + transition = PageTransition::CHAIN_END;
|
| + break;
|
| + default:
|
| + switch (navigation->navigation_qualifier()) {
|
| + case sync_pb::
|
| + TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT:
|
| + transition = PageTransition::CLIENT_REDIRECT;
|
| + break;
|
| + case sync_pb::
|
| + TabNavigation_PageTransitionQualifier_SERVER_REDIRECT:
|
| + transition = PageTransition::SERVER_REDIRECT;
|
| + break;
|
| + default:
|
| + transition = PageTransition::TYPED;
|
| + }
|
| + }
|
| + }
|
| + TabNavigation tab_navigation(index, virtual_url, referrer, title, state,
|
| + transition);
|
| + navigations->insert(navigations->end(), tab_navigation);
|
| +}
|
| +
|
| +void SessionModelAssociator::PopulateSessionTabFromSpecifics(
|
| + SessionTab* session_tab,
|
| + const sync_pb::SessionTab* tab, SessionID id) {
|
| + session_tab->window_id = id;
|
| + SessionID tabID;
|
| + session_tab->tab_id = tabID;
|
| + if (tab->has_tab_visual_index())
|
| + session_tab->tab_visual_index = tab->tab_visual_index();
|
| + if (tab->has_current_navigation_index()) {
|
| + session_tab->current_navigation_index =
|
| + tab->current_navigation_index();
|
| + }
|
| + if (tab->has_pinned())
|
| + session_tab->pinned = tab->pinned();
|
| + if (tab->has_extension_app_id())
|
| + session_tab->extension_app_id = tab->extension_app_id();
|
| + for (int i3 = 0; i3 < tab->navigation_size(); i3++) {
|
| + const sync_pb::TabNavigation* navigation = &tab->navigation(i3);
|
| + AppendSessionTabNavigation(&session_tab->navigations, navigation);
|
| + }
|
| +}
|
| +
|
| +void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
|
| + SessionWindow* session_window, const sync_pb::SessionWindow* window) {
|
| + SessionID id;
|
| + session_window->window_id = id;
|
| + if (window->has_selected_tab_index())
|
| + session_window->selected_tab_index = window->selected_tab_index();
|
| + if (window->has_browser_type()) {
|
| + if (window->browser_type() ==
|
| + sync_pb::SessionWindow_BrowserType_TYPE_NORMAL) {
|
| + session_window->type = 1;
|
| + } else {
|
| + session_window->type = 2;
|
| + }
|
| + }
|
| + for (int i = 0; i < window->session_tab_size(); i++) {
|
| + const sync_pb::SessionTab& tab = window->session_tab(i);
|
| + SessionTab* session_tab = new SessionTab();
|
| + PopulateSessionTabFromSpecifics(session_tab, &tab, id);
|
| + session_window->tabs.insert(session_window->tabs.end(), session_tab);
|
| + }
|
| +}
|
| +
|
| +bool SessionModelAssociator::UpdateSyncModel(
|
| + sync_pb::SessionSpecifics* session_data,
|
| + sync_api::WriteTransaction* trans,
|
| + const sync_api::ReadNode* root) {
|
| + const std::string id = session_data->session_tag();
|
| + sync_api::WriteNode write_node(trans);
|
| + if (!write_node.InitByClientTagLookup(syncable::SESSIONS, id)) {
|
| + sync_api::WriteNode create_node(trans);
|
| + if (!create_node.InitUniqueByCreation(syncable::SESSIONS, *root, id)) {
|
| + LOG(ERROR) << "Could not create node for session " << id;
|
| + return false;
|
| + }
|
| + create_node.SetSessionSpecifics(*session_data);
|
| + return true;
|
| + }
|
| + write_node.SetSessionSpecifics(*session_data);
|
| + return true;
|
| +}
|
| +
|
| +} // namespace browser_sync
|
| +
|
|
|