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/glue/session_model_associator.h" | 5 #include "chrome/browser/sync/glue/session_model_associator.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <set> | 8 #include <set> |
9 #include <utility> | 9 #include <utility> |
10 | 10 |
11 #include "base/logging.h" | 11 #include "base/logging.h" |
12 #include "base/sys_info.h" | |
12 #include "base/tracked.h" | 13 #include "base/tracked.h" |
14 #include "chrome/browser/browser_process.h" | |
13 #include "chrome/browser/profiles/profile.h" | 15 #include "chrome/browser/profiles/profile.h" |
14 #include "chrome/browser/sessions/session_service_factory.h" | 16 #include "chrome/browser/sessions/session_service_factory.h" |
15 #include "chrome/browser/sync/api/sync_error.h" | 17 #include "chrome/browser/sync/api/sync_error.h" |
18 #include "chrome/browser/sync/glue/synced_session.h" | |
16 #include "chrome/browser/sync/glue/synced_tab_delegate.h" | 19 #include "chrome/browser/sync/glue/synced_tab_delegate.h" |
17 #include "chrome/browser/sync/glue/synced_window_delegate.h" | 20 #include "chrome/browser/sync/glue/synced_window_delegate.h" |
18 #include "chrome/browser/sync/internal_api/read_node.h" | 21 #include "chrome/browser/sync/internal_api/read_node.h" |
19 #include "chrome/browser/sync/internal_api/read_transaction.h" | 22 #include "chrome/browser/sync/internal_api/read_transaction.h" |
20 #include "chrome/browser/sync/internal_api/write_node.h" | 23 #include "chrome/browser/sync/internal_api/write_node.h" |
21 #include "chrome/browser/sync/internal_api/write_transaction.h" | 24 #include "chrome/browser/sync/internal_api/write_transaction.h" |
22 #include "chrome/browser/sync/profile_sync_service.h" | 25 #include "chrome/browser/sync/profile_sync_service.h" |
23 #include "chrome/browser/sync/syncable/syncable.h" | 26 #include "chrome/browser/sync/syncable/syncable.h" |
24 #include "chrome/common/chrome_notification_types.h" | 27 #include "chrome/common/chrome_notification_types.h" |
25 #include "chrome/common/url_constants.h" | 28 #include "chrome/common/url_constants.h" |
26 #include "content/browser/tab_contents/navigation_entry.h" | 29 #include "content/browser/tab_contents/navigation_entry.h" |
27 #include "content/common/notification_details.h" | 30 #include "content/common/notification_details.h" |
28 #include "content/common/notification_service.h" | 31 #include "content/common/notification_service.h" |
32 #if defined(OS_LINUX) | |
33 #include "base/linux_util.h" | |
34 #endif | |
29 | 35 |
30 namespace browser_sync { | 36 namespace browser_sync { |
31 | 37 |
32 namespace { | 38 namespace { |
33 static const char kNoSessionsFolderError[] = | 39 static const char kNoSessionsFolderError[] = |
34 "Server did not create the top-level sessions node. We " | 40 "Server did not create the top-level sessions node. We " |
35 "might be running against an out-of-date server."; | 41 "might be running against an out-of-date server."; |
36 | 42 |
37 // The maximum number of navigations in each direction we care to sync. | 43 // The maximum number of navigations in each direction we care to sync. |
38 static const int max_sync_navigation_count = 6; | 44 static const int max_sync_navigation_count = 6; |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
116 } | 122 } |
117 | 123 |
118 void SessionModelAssociator::ReassociateWindows(bool reload_tabs) { | 124 void SessionModelAssociator::ReassociateWindows(bool reload_tabs) { |
119 DCHECK(CalledOnValidThread()); | 125 DCHECK(CalledOnValidThread()); |
120 std::string local_tag = GetCurrentMachineTag(); | 126 std::string local_tag = GetCurrentMachineTag(); |
121 sync_pb::SessionSpecifics specifics; | 127 sync_pb::SessionSpecifics specifics; |
122 specifics.set_session_tag(local_tag); | 128 specifics.set_session_tag(local_tag); |
123 sync_pb::SessionHeader* header_s = specifics.mutable_header(); | 129 sync_pb::SessionHeader* header_s = specifics.mutable_header(); |
124 SyncedSession* current_session = | 130 SyncedSession* current_session = |
125 synced_session_tracker_.GetSession(local_tag); | 131 synced_session_tracker_.GetSession(local_tag); |
132 header_s->set_client_name(current_session_name_); | |
133 #if defined(OS_LINUX) | |
134 header_s->set_device_type(sync_pb::SessionHeader_DeviceType_TYPE_LINUX); | |
135 #elif defined(OS_MACOSX) | |
136 header_s->set_device_type(sync_pb::SessionHeader_DeviceType_TYPE_MAC); | |
137 #elif defined(OS_WIN) | |
138 header_s->set_device_type(sync_pb::SessionHeader_DeviceType_TYPE_WIN); | |
139 #elif defined(OS_CHROMEOS) | |
140 header_s->set_device_type(sync_pb::SessionHeader_DeviceType_TYPE_CROS); | |
141 #else | |
142 header_s->set_device_type(sync_pb::SessionHeader_DeviceType_TYPE_PC); | |
Nicolas Zea
2011/09/01 00:03:36
Shouldn't this be TYPE_OTHER?
Yaron
2011/09/01 20:16:17
Done.
| |
143 #endif | |
126 | 144 |
127 size_t window_num = 0; | 145 size_t window_num = 0; |
128 std::set<SyncedWindowDelegate*> windows = | 146 std::set<SyncedWindowDelegate*> windows = |
129 SyncedWindowDelegate::GetSyncedWindowDelegates(); | 147 SyncedWindowDelegate::GetSyncedWindowDelegates(); |
130 current_session->windows.reserve(windows.size()); | 148 current_session->windows.reserve(windows.size()); |
131 for (std::set<SyncedWindowDelegate*>::const_iterator i = | 149 for (std::set<SyncedWindowDelegate*>::const_iterator i = |
132 windows.begin(); i != windows.end(); ++i) { | 150 windows.begin(); i != windows.end(); ++i) { |
133 // Make sure the window has tabs and a viewable window. The viewable window | 151 // Make sure the window has tabs and a viewable window. The viewable window |
134 // check is necessary because, for example, when a browser is closed the | 152 // check is necessary because, for example, when a browser is closed the |
135 // destructor is not necessarily run immediately. This means its possible | 153 // destructor is not necessarily run immediately. This means its possible |
(...skipping 284 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
420 { | 438 { |
421 sync_api::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); | 439 sync_api::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
422 | 440 |
423 sync_api::ReadNode root(&trans); | 441 sync_api::ReadNode root(&trans); |
424 if (!root.InitByTagLookup(kSessionsTag)) { | 442 if (!root.InitByTagLookup(kSessionsTag)) { |
425 error->Reset(FROM_HERE, kNoSessionsFolderError, model_type()); | 443 error->Reset(FROM_HERE, kNoSessionsFolderError, model_type()); |
426 return false; | 444 return false; |
427 } | 445 } |
428 | 446 |
429 // Make sure we have a machine tag. | 447 // Make sure we have a machine tag. |
430 if (current_machine_tag_.empty()) | 448 if (current_machine_tag_.empty()) { |
431 InitializeCurrentMachineTag(&trans); | 449 InitializeCurrentMachineTag(&trans); |
450 // The session name is retrieved asynchronously so it might not come back | |
451 // for the writing of the session. However, we write to the session often | |
452 // enough (on every navigation) that we'll pick it up quickly. | |
453 InitializeCurrentSessionName(); | |
454 } | |
432 synced_session_tracker_.SetLocalSessionTag(current_machine_tag_); | 455 synced_session_tracker_.SetLocalSessionTag(current_machine_tag_); |
433 if (!UpdateAssociationsFromSyncModel(root, &trans)) { | 456 if (!UpdateAssociationsFromSyncModel(root, &trans)) { |
434 error->Reset(FROM_HERE, | 457 error->Reset(FROM_HERE, |
435 "Failed to update associations from sync", | 458 "Failed to update associations from sync", |
436 model_type()); | 459 model_type()); |
437 return false; | 460 return false; |
438 } | 461 } |
439 | 462 |
440 if (local_session_syncid_ == sync_api::kInvalidId) { | 463 if (local_session_syncid_ == sync_api::kInvalidId) { |
441 // The sync db didn't have a header node for us, we need to create one. | 464 // The sync db didn't have a header node for us, we need to create one. |
(...skipping 17 matching lines...) Expand all Loading... | |
459 | 482 |
460 return true; | 483 return true; |
461 } | 484 } |
462 | 485 |
463 bool SessionModelAssociator::DisassociateModels(SyncError* error) { | 486 bool SessionModelAssociator::DisassociateModels(SyncError* error) { |
464 DCHECK(CalledOnValidThread()); | 487 DCHECK(CalledOnValidThread()); |
465 synced_session_tracker_.clear(); | 488 synced_session_tracker_.clear(); |
466 tab_map_.clear(); | 489 tab_map_.clear(); |
467 tab_pool_.clear(); | 490 tab_pool_.clear(); |
468 local_session_syncid_ = sync_api::kInvalidId; | 491 local_session_syncid_ = sync_api::kInvalidId; |
492 current_machine_tag_ = ""; | |
493 current_session_name_ = ""; | |
469 | 494 |
470 // There is no local model stored with which to disassociate, just notify | 495 // There is no local model stored with which to disassociate, just notify |
471 // foreign session handlers. | 496 // foreign session handlers. |
472 NotificationService::current()->Notify( | 497 NotificationService::current()->Notify( |
473 chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED, | 498 chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED, |
474 NotificationService::AllSources(), | 499 NotificationService::AllSources(), |
475 NotificationService::NoDetails()); | 500 NotificationService::NoDetails()); |
476 return true; | 501 return true; |
477 } | 502 } |
478 | 503 |
479 void SessionModelAssociator::InitializeCurrentMachineTag( | 504 void SessionModelAssociator::InitializeCurrentMachineTag( |
480 sync_api::WriteTransaction* trans) { | 505 sync_api::WriteTransaction* trans) { |
481 DCHECK(CalledOnValidThread()); | 506 DCHECK(CalledOnValidThread()); |
482 syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory(); | 507 syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory(); |
483 | |
484 // TODO(zea): We need a better way of creating a machine tag. The directory | |
485 // kernel's cache_guid changes every time syncing is turned on and off. This | |
486 // will result in session's associated with stale machine tags persisting on | |
487 // the server since that tag will not be reused. Eventually this should | |
488 // become some string identifiable to the user. (Home, Work, Laptop, etc.) | |
489 // See issue at http://crbug.com/59672 | |
490 current_machine_tag_ = "session_sync"; | 508 current_machine_tag_ = "session_sync"; |
491 current_machine_tag_.append(dir->cache_guid()); | 509 current_machine_tag_.append(dir->cache_guid()); |
492 VLOG(1) << "Creating machine tag: " << current_machine_tag_; | 510 VLOG(1) << "Creating machine tag: " << current_machine_tag_; |
493 tab_pool_.set_machine_tag(current_machine_tag_); | 511 tab_pool_.set_machine_tag(current_machine_tag_); |
494 } | 512 } |
495 | 513 |
514 void SessionModelAssociator::OnSessionNameInitialized(const std::string name) { | |
515 DCHECK(CalledOnValidThread()); | |
516 // Only use the default machine name if it hasn't already been set. | |
517 if (current_session_name_.empty()) { | |
518 current_session_name_ = name; | |
519 } | |
520 } | |
521 | |
522 // Task which runs on the file thread because it runs system calls which can | |
523 // block while retrieving sytem information. | |
524 class GetSessionNameTask : public Task { | |
525 public: | |
526 explicit GetSessionNameTask( | |
527 const WeakHandle<SessionModelAssociator> associator) : | |
528 associator_(associator) {} | |
529 | |
530 virtual void Run() { | |
531 #if defined(OS_LINUX) | |
532 std::string session_name = base::GetLinuxDistro(); | |
533 #elif defined(OS_MACOSX) | |
534 std::string session_name = GetHardwareModelName(); | |
535 #elif defined(OS_WIN) | |
536 std::string session_name = GetComputerName(); | |
537 #else | |
538 std::string session_name; | |
539 #endif | |
540 if (session_name == "Unknown" || session_name.empty()) { | |
541 session_name = base::SysInfo::OperatingSystemName(); | |
542 } | |
543 associator_.Call(FROM_HERE, | |
544 &SessionModelAssociator::OnSessionNameInitialized, | |
545 session_name); | |
546 } | |
547 const WeakHandle<SessionModelAssociator> associator_; | |
548 | |
549 DISALLOW_COPY_AND_ASSIGN(GetSessionNameTask); | |
550 }; | |
551 | |
552 void SessionModelAssociator::InitializeCurrentSessionName() { | |
553 DCHECK(CalledOnValidThread()); | |
554 #if defined(OS_CHROMEOS) | |
555 OnSessionNameInitialized("Chromebook"); | |
556 #else | |
557 if (setup_for_test_) { | |
558 OnSessionNameInitialized("TestSessionName"); | |
559 } else { | |
560 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, | |
Nicolas Zea
2011/09/01 00:03:36
BrowserThread::PostTask(BrowserThread::FILE, ...)
Yaron
2011/09/01 20:16:17
Done.
| |
561 new GetSessionNameTask(MakeWeakHandle(AsWeakPtr()))); | |
562 } | |
563 #endif | |
564 } | |
565 | |
496 bool SessionModelAssociator::UpdateAssociationsFromSyncModel( | 566 bool SessionModelAssociator::UpdateAssociationsFromSyncModel( |
497 const sync_api::ReadNode& root, | 567 const sync_api::ReadNode& root, |
498 const sync_api::BaseTransaction* trans) { | 568 const sync_api::BaseTransaction* trans) { |
499 DCHECK(CalledOnValidThread()); | 569 DCHECK(CalledOnValidThread()); |
500 | 570 |
501 // Iterate through the nodes and associate any foreign sessions. | 571 // Iterate through the nodes and associate any foreign sessions. |
502 int64 id = root.GetFirstChildId(); | 572 int64 id = root.GetFirstChildId(); |
503 while (id != sync_api::kInvalidId) { | 573 while (id != sync_api::kInvalidId) { |
504 sync_api::ReadNode sync_node(trans); | 574 sync_api::ReadNode sync_node(trans); |
505 if (!sync_node.InitByIdLookup(id)) { | 575 if (!sync_node.InitByIdLookup(id)) { |
506 LOG(ERROR) << "Failed to fetch sync node for id " << id; | 576 LOG(ERROR) << "Failed to fetch sync node for id " << id; |
507 return false; | 577 return false; |
508 } | 578 } |
509 | 579 |
510 const sync_pb::SessionSpecifics& specifics = | 580 const sync_pb::SessionSpecifics& specifics = |
511 sync_node.GetSessionSpecifics(); | 581 sync_node.GetSessionSpecifics(); |
512 const int64 modification_time = sync_node.GetModificationTime(); | 582 const int64 modification_time = sync_node.GetModificationTime(); |
513 if (specifics.session_tag() != GetCurrentMachineTag()) { | 583 if (specifics.session_tag() != GetCurrentMachineTag()) { |
514 if (!AssociateForeignSpecifics(specifics, modification_time)) { | 584 if (!AssociateForeignSpecifics(specifics, modification_time)) { |
515 return false; | 585 return false; |
516 } | 586 } |
517 } else if (id != local_session_syncid_) { | 587 } else if (id != local_session_syncid_) { |
518 // This is previously stored local session information. | 588 // This is previously stored local session information. |
519 if (specifics.has_header()) { | 589 if (specifics.has_header()) { |
520 if (sync_api::kInvalidId != local_session_syncid_) | 590 if (sync_api::kInvalidId != local_session_syncid_) |
521 return false; | 591 return false; |
522 | 592 |
523 // This is our previous header node, reuse it. | 593 // This is our previous header node, reuse it. |
524 local_session_syncid_ = id; | 594 local_session_syncid_ = id; |
Nicolas Zea
2011/09/01 00:03:36
We should probably overwrite the local session nam
Yaron
2011/09/01 20:16:17
Done.
| |
525 } else { | 595 } else { |
526 if (!specifics.has_tab()) | 596 if (!specifics.has_tab()) |
527 return false; | 597 return false; |
528 | 598 |
529 // This is a tab node. We want to track these to reuse them in our free | 599 // This is a tab node. We want to track these to reuse them in our free |
530 // tab node pool. They will be overwritten eventually, so need to do | 600 // tab node pool. They will be overwritten eventually, so need to do |
531 // anything else. | 601 // anything else. |
532 tab_pool_.AddTabNode(id); | 602 tab_pool_.AddTabNode(id); |
533 } | 603 } |
534 } | 604 } |
(...skipping 19 matching lines...) Expand all Loading... | |
554 if (specifics.has_header()) { | 624 if (specifics.has_header()) { |
555 // Read in the header data for this foreign session. | 625 // Read in the header data for this foreign session. |
556 // Header data contains window information and ordered tab id's for each | 626 // Header data contains window information and ordered tab id's for each |
557 // window. | 627 // window. |
558 | 628 |
559 // Load (or create) the SyncedSession object for this client. | 629 // Load (or create) the SyncedSession object for this client. |
560 SyncedSession* foreign_session = | 630 SyncedSession* foreign_session = |
561 synced_session_tracker_.GetSession(foreign_session_tag); | 631 synced_session_tracker_.GetSession(foreign_session_tag); |
562 | 632 |
563 const sync_pb::SessionHeader& header = specifics.header(); | 633 const sync_pb::SessionHeader& header = specifics.header(); |
634 PopulateSessionHeaderFromSpecifics(header, foreign_session); | |
564 foreign_session->windows.reserve(header.window_size()); | 635 foreign_session->windows.reserve(header.window_size()); |
565 VLOG(1) << "Associating " << foreign_session_tag << " with " << | 636 VLOG(1) << "Associating " << foreign_session_tag << " with " << |
566 header.window_size() << " windows."; | 637 header.window_size() << " windows."; |
567 size_t i; | 638 size_t i; |
568 for (i = 0; i < static_cast<size_t>(header.window_size()); ++i) { | 639 for (i = 0; i < static_cast<size_t>(header.window_size()); ++i) { |
569 if (i >= foreign_session->windows.size()) { | 640 if (i >= foreign_session->windows.size()) { |
570 // This a new window, create it. | 641 // This a new window, create it. |
571 foreign_session->windows.push_back(new SessionWindow()); | 642 foreign_session->windows.push_back(new SessionWindow()); |
572 } | 643 } |
573 const sync_pb::SessionWindow& window_s = header.window(i); | 644 const sync_pb::SessionWindow& window_s = header.window(i); |
(...skipping 24 matching lines...) Expand all Loading... | |
598 return true; | 669 return true; |
599 } | 670 } |
600 | 671 |
601 void SessionModelAssociator::DisassociateForeignSession( | 672 void SessionModelAssociator::DisassociateForeignSession( |
602 const std::string& foreign_session_tag) { | 673 const std::string& foreign_session_tag) { |
603 DCHECK(CalledOnValidThread()); | 674 DCHECK(CalledOnValidThread()); |
604 synced_session_tracker_.DeleteSession(foreign_session_tag); | 675 synced_session_tracker_.DeleteSession(foreign_session_tag); |
605 } | 676 } |
606 | 677 |
607 // Static | 678 // Static |
679 void SessionModelAssociator::PopulateSessionHeaderFromSpecifics( | |
680 const sync_pb::SessionHeader& header_specifics, | |
681 SyncedSession* session_header) { | |
682 if (header_specifics.has_client_name()) { | |
683 session_header->client_name = header_specifics.client_name(); | |
684 } | |
685 if (header_specifics.has_device_type()) { | |
686 switch (header_specifics.device_type()) { | |
687 case sync_pb::SessionHeader_DeviceType_TYPE_WIN: | |
688 session_header->device_type = SyncedSession::TYPE_WIN; | |
689 break; | |
690 case sync_pb::SessionHeader_DeviceType_TYPE_MAC: | |
691 session_header->device_type = SyncedSession::TYPE_MACOSX; | |
692 break; | |
693 case sync_pb::SessionHeader_DeviceType_TYPE_LINUX: | |
694 session_header->device_type = SyncedSession::TYPE_LINUX; | |
695 break; | |
696 case sync_pb::SessionHeader_DeviceType_TYPE_CROS: | |
697 session_header->device_type = SyncedSession::TYPE_CHROMEOS; | |
698 break; | |
699 case sync_pb::SessionHeader_DeviceType_TYPE_OTHER: | |
700 // Intentionally fall-through | |
701 default: | |
702 session_header->device_type = SyncedSession::TYPE_OTHER; | |
703 break; | |
704 } | |
705 } | |
706 } | |
707 | |
708 // Static | |
608 void SessionModelAssociator::PopulateSessionWindowFromSpecifics( | 709 void SessionModelAssociator::PopulateSessionWindowFromSpecifics( |
609 const std::string& session_tag, | 710 const std::string& session_tag, |
610 const sync_pb::SessionWindow& specifics, | 711 const sync_pb::SessionWindow& specifics, |
611 int64 mtime, | 712 int64 mtime, |
612 SessionWindow* session_window, | 713 SessionWindow* session_window, |
613 SyncedSessionTracker* tracker) { | 714 SyncedSessionTracker* tracker) { |
614 if (specifics.has_window_id()) | 715 if (specifics.has_window_id()) |
615 session_window->window_id.set_id(specifics.window_id()); | 716 session_window->window_id.set_id(specifics.window_id()); |
616 if (specifics.has_selected_tab_index()) | 717 if (specifics.has_selected_tab_index()) |
617 session_window->selected_tab_index = specifics.selected_tab_index(); | 718 session_window->selected_tab_index = specifics.selected_tab_index(); |
(...skipping 451 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1069 | 1170 |
1070 bool SessionModelAssociator::CryptoReadyIfNecessary() { | 1171 bool SessionModelAssociator::CryptoReadyIfNecessary() { |
1071 // We only access the cryptographer while holding a transaction. | 1172 // We only access the cryptographer while holding a transaction. |
1072 sync_api::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); | 1173 sync_api::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare()); |
1073 syncable::ModelTypeSet encrypted_types; | 1174 syncable::ModelTypeSet encrypted_types; |
1074 encrypted_types = sync_api::GetEncryptedTypes(&trans); | 1175 encrypted_types = sync_api::GetEncryptedTypes(&trans); |
1075 return encrypted_types.count(syncable::SESSIONS) == 0 || | 1176 return encrypted_types.count(syncable::SESSIONS) == 0 || |
1076 sync_service_->IsCryptographerReady(&trans); | 1177 sync_service_->IsCryptographerReady(&trans); |
1077 } | 1178 } |
1078 | 1179 |
1180 #if defined(OS_WIN) | |
1181 std::string SessionModelAssociator::GetComputerName() { | |
Nicolas Zea
2011/09/01 00:03:36
Given that these are not safe to run on the UI thr
Yaron
2011/09/01 20:16:17
Done. Made them static in the interim
| |
1182 char computer_name[MAX_COMPUTERNAME_LENGTH + 1]; | |
1183 DWORD size = sizeof(computer_name); | |
1184 if (GetComputerNameA(computer_name, &size)) { | |
1185 return computer_name; | |
1186 } | |
1187 return std::string(); | |
1188 } | |
1189 #endif | |
1190 | |
1191 #if defined(OS_MACOSX) | |
1192 std::string SessionModelAssociator::GetHardwareModelName() { | |
1193 char modelBuffer[256]; | |
1194 size_t length = sizeof(modelBuffer); | |
1195 if (!sysctlbyname("hw.model", modelBuffer, &length, NULL, 0)) { | |
1196 for (size_t i = 0; i < length; i++) { | |
1197 if (IsAsciiDigit(modelBuffer[i])) { | |
1198 return std::string(modelBuffer, 0, i); | |
1199 } | |
1200 } | |
1201 return std::string(modelBuffer, 0, length); | |
1202 } else { | |
1203 return "Unknown"; | |
1204 } | |
1205 } | |
1206 #endif | |
1207 | |
1079 } // namespace browser_sync | 1208 } // namespace browser_sync |
OLD | NEW |