1/* 2 * Copyright 2011-2014 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel D��rfler, axeld@pinc-software.de 7 * Hamish Morrison, hamish@lavabit.com 8 * John Scipione, jscipione@gmail.com 9 */ 10 11 12#include "NetworkTimeView.h" 13 14#include <ctype.h> 15#include <stdio.h> 16#include <string.h> 17 18#include <Alert.h> 19#include <Button.h> 20#include <Catalog.h> 21#include <CheckBox.h> 22#include <File.h> 23#include <FindDirectory.h> 24#include <Invoker.h> 25#include <ListItem.h> 26#include <ListView.h> 27#include <Path.h> 28#include <ScrollView.h> 29#include <Size.h> 30#include <TextControl.h> 31 32#include "ntp.h" 33#include "TimeMessages.h" 34 35 36#undef B_TRANSLATION_CONTEXT 37#define B_TRANSLATION_CONTEXT "Time" 38 39 40// #pragma mark - Settings 41 42 43Settings::Settings() 44 : 45 fMessage(kMsgNetworkTimeSettings) 46{ 47 ResetToDefaults(); 48 Load(); 49} 50 51 52Settings::~Settings() 53{ 54 Save(); 55} 56 57 58void 59Settings::AddServer(const char* server) 60{ 61 if (_GetStringByValue("server", server) == B_ERROR) 62 fMessage.AddString("server", server); 63} 64 65 66const char* 67Settings::GetServer(int32 index) const 68{ 69 const char* server; 70 fMessage.FindString("server", index, &server); 71 return server; 72} 73 74 75void 76Settings::RemoveServer(const char* server) 77{ 78 int32 index = _GetStringByValue("server", server); 79 if (index != B_ERROR) { 80 fMessage.RemoveData("server", index); 81 82 int32 count; 83 fMessage.GetInfo("server", NULL, &count); 84 if (GetDefaultServer() >= count) 85 SetDefaultServer(count - 1); 86 } 87} 88 89 90void 91Settings::SetDefaultServer(int32 index) 92{ 93 if (fMessage.ReplaceInt32("default server", index) != B_OK) 94 fMessage.AddInt32("default server", index); 95} 96 97 98int32 99Settings::GetDefaultServer() const 100{ 101 int32 index; 102 fMessage.FindInt32("default server", &index); 103 return index; 104} 105 106 107void 108Settings::SetTryAllServers(bool boolean) 109{ 110 fMessage.ReplaceBool("try all servers", boolean); 111} 112 113 114bool 115Settings::GetTryAllServers() const 116{ 117 bool boolean; 118 fMessage.FindBool("try all servers", &boolean); 119 return boolean; 120} 121 122 123void 124Settings::SetSynchronizeAtBoot(bool boolean) 125{ 126 fMessage.ReplaceBool("synchronize at boot", boolean); 127} 128 129 130bool 131Settings::GetSynchronizeAtBoot() const 132{ 133 bool boolean; 134 fMessage.FindBool("synchronize at boot", &boolean); 135 return boolean; 136} 137 138 139void 140Settings::ResetServersToDefaults() 141{ 142 fMessage.RemoveName("server"); 143 144 fMessage.AddString("server", "pool.ntp.org"); 145 fMessage.AddString("server", "de.pool.ntp.org"); 146 fMessage.AddString("server", "time.nist.gov"); 147 148 if (fMessage.ReplaceInt32("default server", 0) != B_OK) 149 fMessage.AddInt32("default server", 0); 150} 151 152 153void 154Settings::ResetToDefaults() 155{ 156 fMessage.MakeEmpty(); 157 ResetServersToDefaults(); 158 159 fMessage.AddBool("synchronize at boot", true); 160 fMessage.AddBool("try all servers", true); 161} 162 163 164void 165Settings::Revert() 166{ 167 fMessage = fOldMessage; 168} 169 170 171bool 172Settings::SettingsChanged() 173{ 174 ssize_t oldSize = fOldMessage.FlattenedSize(); 175 ssize_t newSize = fMessage.FlattenedSize(); 176 177 if (oldSize != newSize || oldSize < 0 || newSize < 0) 178 return true; 179 180 char* oldBytes = new (std::nothrow) char[oldSize]; 181 if (oldBytes == NULL) 182 return true; 183 184 fOldMessage.Flatten(oldBytes, oldSize); 185 char* newBytes = new (std::nothrow) char[newSize]; 186 if (newBytes == NULL) { 187 delete[] oldBytes; 188 return true; 189 } 190 fMessage.Flatten(newBytes, newSize); 191 192 int result = memcmp(oldBytes, newBytes, oldSize); 193 194 delete[] oldBytes; 195 delete[] newBytes; 196 197 return result != 0; 198} 199 200 201status_t 202Settings::Load() 203{ 204 status_t status; 205 206 BPath path; 207 if ((status = _GetPath(path)) != B_OK) 208 return status; 209 210 BFile file(path.Path(), B_READ_ONLY); 211 if ((status = file.InitCheck()) != B_OK) 212 return status; 213 214 BMessage load; 215 if ((status = load.Unflatten(&file)) != B_OK) 216 return status; 217 218 if (load.what != kMsgNetworkTimeSettings) 219 return B_BAD_TYPE; 220 221 fMessage = load; 222 fOldMessage = fMessage; 223 return B_OK; 224} 225 226 227status_t 228Settings::Save() 229{ 230 status_t status; 231 232 BPath path; 233 if ((status = _GetPath(path)) != B_OK) 234 return status; 235 236 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 237 if ((status = file.InitCheck()) != B_OK) 238 return status; 239 240 file.SetSize(0); 241 242 return fMessage.Flatten(&file); 243} 244 245 246int32 247Settings::_GetStringByValue(const char* name, const char* value) 248{ 249 const char* string; 250 for (int32 index = 0; fMessage.FindString(name, index, &string) == B_OK; 251 index++) { 252 if (strcmp(string, value) == 0) 253 return index; 254 } 255 256 return B_ERROR; 257} 258 259 260status_t 261Settings::_GetPath(BPath& path) 262{ 263 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 264 if (status != B_OK) 265 return status; 266 267 path.Append("networktime settings"); 268 269 return B_OK; 270} 271 272 273// #pragma mark - NetworkTimeView 274 275 276NetworkTimeView::NetworkTimeView(const char* name) 277 : 278 BGroupView(name, B_VERTICAL, B_USE_DEFAULT_SPACING), 279 fSettings(), 280 fServerTextControl(NULL), 281 fAddButton(NULL), 282 fRemoveButton(NULL), 283 fResetButton(NULL), 284 fServerListView(NULL), 285 fTryAllServersCheckBox(NULL), 286 fSynchronizeAtBootCheckBox(NULL), 287 fSynchronizeButton(NULL), 288 fTextColor(ui_color(B_CONTROL_TEXT_COLOR)), 289 fInvalidColor(ui_color(B_FAILURE_COLOR)), 290 fUpdateThread(-1) 291{ 292 fSettings.Load(); 293 _InitView(); 294} 295 296 297NetworkTimeView::~NetworkTimeView() 298{ 299 delete fServerTextControl; 300 delete fAddButton; 301 delete fRemoveButton; 302 delete fResetButton; 303 delete fServerListView; 304 delete fTryAllServersCheckBox; 305 delete fSynchronizeAtBootCheckBox; 306 delete fSynchronizeButton; 307} 308 309 310void 311NetworkTimeView::MessageReceived(BMessage* message) 312{ 313 switch (message->what) { 314 case kMsgSetDefaultServer: 315 { 316 int32 currentSelection = fServerListView->CurrentSelection(); 317 if (currentSelection < 0) 318 fServerListView->Select(fSettings.GetDefaultServer()); 319 else { 320 fSettings.SetDefaultServer(currentSelection); 321 Looper()->PostMessage(new BMessage(kMsgChange)); 322 } 323 break; 324 } 325 326 case kMsgServerEdited: 327 { 328 bool isValid = _IsValidServerName(fServerTextControl->Text()); 329 fServerTextControl->TextView()->SetFontAndColor(0, 330 fServerTextControl->TextView()->TextLength(), NULL, 0, 331 isValid ? &fTextColor : &fInvalidColor); 332 fAddButton->SetEnabled(isValid); 333 break; 334 } 335 336 case kMsgAddServer: 337 if (!_IsValidServerName(fServerTextControl->Text())) 338 break; 339 340 fSettings.AddServer(fServerTextControl->Text()); 341 _UpdateServerList(); 342 fServerTextControl->SetText(""); 343 Looper()->PostMessage(new BMessage(kMsgChange)); 344 break; 345 346 case kMsgRemoveServer: 347 { 348 int32 currentSelection = fServerListView->CurrentSelection(); 349 if (currentSelection < 0) 350 break; 351 352 fSettings.RemoveServer(((BStringItem*) 353 fServerListView->ItemAt(currentSelection))->Text()); 354 _UpdateServerList(); 355 Looper()->PostMessage(new BMessage(kMsgChange)); 356 break; 357 } 358 359 case kMsgResetServerList: 360 fSettings.ResetServersToDefaults(); 361 _UpdateServerList(); 362 Looper()->PostMessage(new BMessage(kMsgChange)); 363 break; 364 365 case kMsgTryAllServers: 366 fSettings.SetTryAllServers( 367 fTryAllServersCheckBox->Value()); 368 Looper()->PostMessage(new BMessage(kMsgChange)); 369 break; 370 371 case kMsgSynchronizeAtBoot: 372 fSettings.SetSynchronizeAtBoot(fSynchronizeAtBootCheckBox->Value()); 373 Looper()->PostMessage(new BMessage(kMsgChange)); 374 break; 375 376 case kMsgStopSynchronization: 377 if (fUpdateThread >= B_OK) 378 kill_thread(fUpdateThread); 379 380 _DoneSynchronizing(); 381 break; 382 383 case kMsgSynchronize: 384 { 385 if (fUpdateThread >= B_OK) 386 break; 387 388 BMessenger* messenger = new BMessenger(this); 389 update_time(fSettings, messenger, &fUpdateThread); 390 fSynchronizeButton->SetLabel(B_TRANSLATE("Stop")); 391 fSynchronizeButton->Message()->what = kMsgStopSynchronization; 392 break; 393 } 394 395 case kMsgSynchronizationResult: 396 { 397 _DoneSynchronizing(); 398 399 status_t status; 400 if (message->FindInt32("status", (int32 *)&status) == B_OK) { 401 if (status == B_OK) 402 return; 403 404 const char* errorString; 405 message->FindString("error string", &errorString); 406 char buffer[256]; 407 408 int32 errorCode; 409 if (message->FindInt32("error code", &errorCode) == B_OK) { 410 snprintf(buffer, sizeof(buffer), 411 B_TRANSLATE("The following error occured " 412 "while synchronizing:\n%s: %s"), 413 errorString, strerror(errorCode)); 414 } else { 415 snprintf(buffer, sizeof(buffer), 416 B_TRANSLATE("The following error occured " 417 "while synchronizing:\n%s"), 418 errorString); 419 } 420 421 BAlert* alert = new BAlert(B_TRANSLATE("Time"), buffer, 422 B_TRANSLATE("OK")); 423 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 424 alert->Go(); 425 } 426 break; 427 } 428 429 case kMsgRevert: 430 fSettings.Revert(); 431 fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers()); 432 fSynchronizeAtBootCheckBox->SetValue( 433 fSettings.GetSynchronizeAtBoot()); 434 _UpdateServerList(); 435 break; 436 437 default: 438 BGroupView::MessageReceived(message); 439 break; 440 } 441} 442 443 444void 445NetworkTimeView::AttachedToWindow() 446{ 447 fServerTextControl->SetTarget(this); 448 fServerListView->SetTarget(this); 449 fAddButton->SetTarget(this); 450 fAddButton->SetEnabled(false); 451 fRemoveButton->SetTarget(this); 452 fResetButton->SetTarget(this); 453 fTryAllServersCheckBox->SetTarget(this); 454 fSynchronizeAtBootCheckBox->SetTarget(this); 455 fSynchronizeButton->SetTarget(this); 456} 457 458 459bool 460NetworkTimeView::CheckCanRevert() 461{ 462 return fSettings.SettingsChanged(); 463} 464 465 466void 467NetworkTimeView::_InitView() 468{ 469 fServerTextControl = new BTextControl(NULL, NULL, 470 new BMessage(kMsgAddServer)); 471 fServerTextControl->SetModificationMessage(new BMessage(kMsgServerEdited)); 472 473 const float kButtonWidth = fServerTextControl->Frame().Height(); 474 475 fAddButton = new BButton("add", "+", new BMessage(kMsgAddServer)); 476 fAddButton->SetToolTip(B_TRANSLATE("Add")); 477 fAddButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth)); 478 479 fRemoveButton = new BButton("remove", "���", new BMessage(kMsgRemoveServer)); 480 fRemoveButton->SetToolTip(B_TRANSLATE("Remove")); 481 fRemoveButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth)); 482 483 fServerListView = new BListView("serverList"); 484 fServerListView->SetExplicitMinSize(BSize(B_SIZE_UNSET, kButtonWidth * 4)); 485 fServerListView->SetSelectionMessage(new BMessage(kMsgSetDefaultServer)); 486 BScrollView* scrollView = new BScrollView("serverScrollView", 487 fServerListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true); 488 _UpdateServerList(); 489 490 fTryAllServersCheckBox = new BCheckBox("tryAllServers", 491 B_TRANSLATE("Try all servers"), new BMessage(kMsgTryAllServers)); 492 fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers()); 493 494 fSynchronizeAtBootCheckBox = new BCheckBox("autoUpdate", 495 B_TRANSLATE("Synchronize at boot"), 496 new BMessage(kMsgSynchronizeAtBoot)); 497 fSynchronizeAtBootCheckBox->SetValue(fSettings.GetSynchronizeAtBoot()); 498 499 fResetButton = new BButton("reset", 500 B_TRANSLATE("Reset to default server list"), 501 new BMessage(kMsgResetServerList)); 502 503 fSynchronizeButton = new BButton("update", B_TRANSLATE("Synchronize"), 504 new BMessage(kMsgSynchronize)); 505 506 BLayoutBuilder::Group<>(this, B_VERTICAL) 507 .AddGroup(B_VERTICAL, B_USE_SMALL_SPACING) 508 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING) 509 .Add(fServerTextControl) 510 .Add(fAddButton) 511 .End() 512 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING) 513 .Add(scrollView) 514 .AddGroup(B_VERTICAL, B_USE_SMALL_SPACING) 515 .Add(fRemoveButton) 516 .AddGlue() 517 .End() 518 .End() 519 .End() 520 .AddGroup(B_HORIZONTAL) 521 .AddGroup(B_VERTICAL, 0) 522 .Add(fTryAllServersCheckBox) 523 .Add(fSynchronizeAtBootCheckBox) 524 .End() 525 .End() 526 .AddGroup(B_HORIZONTAL) 527 .Add(fResetButton) 528 .AddGlue() 529 .Add(fSynchronizeButton) 530 .End() 531 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 532 B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING); 533} 534 535 536void 537NetworkTimeView::_UpdateServerList() 538{ 539 BListItem* item; 540 while ((item = fServerListView->RemoveItem((int32)0)) != NULL) 541 delete item; 542 543 const char* server; 544 int32 index = 0; 545 while ((server = fSettings.GetServer(index++)) != NULL) 546 fServerListView->AddItem(new BStringItem(server)); 547 548 fServerListView->Select(fSettings.GetDefaultServer()); 549 fServerListView->ScrollToSelection(); 550 551 fRemoveButton->SetEnabled(fServerListView->CountItems() > 0); 552} 553 554 555void 556NetworkTimeView::_DoneSynchronizing() 557{ 558 fUpdateThread = -1; 559 fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again")); 560 fSynchronizeButton->Message()->what = kMsgSynchronize; 561} 562 563 564bool 565NetworkTimeView::_IsValidServerName(const char* serverName) 566{ 567 if (serverName == NULL || *serverName == '\0') 568 return false; 569 570 for (int32 i = 0; serverName[i] != '\0'; i++) { 571 char c = serverName[i]; 572 // Simple URL validation, no scheme should be present 573 if (!(isalnum(c) || c == '.' || c == '-' || c == '_')) 574 return false; 575 } 576 577 return true; 578} 579 580 581// #pragma mark - update functions 582 583 584int32 585update_thread(void* params) 586{ 587 BList* list = (BList*)params; 588 BMessenger* messenger = (BMessenger*)list->ItemAt(1); 589 590 const char* errorString = NULL; 591 int32 errorCode = 0; 592 status_t status = update_time(*(Settings*)list->ItemAt(0), 593 &errorString, &errorCode); 594 595 BMessage result(kMsgSynchronizationResult); 596 result.AddInt32("status", status); 597 result.AddString("error string", errorString); 598 if (errorCode != 0) 599 result.AddInt32("error code", errorCode); 600 601 messenger->SendMessage(&result); 602 delete messenger; 603 604 return B_OK; 605} 606 607 608status_t 609update_time(const Settings& settings, BMessenger* messenger, 610 thread_id* thread) 611{ 612 BList* params = new BList(2); 613 params->AddItem((void*)&settings); 614 params->AddItem((void*)messenger); 615 *thread = spawn_thread(update_thread, "ntpUpdate", 64, params); 616 617 return resume_thread(*thread); 618} 619 620 621status_t 622update_time(const Settings& settings, const char** errorString, 623 int32* errorCode) 624{ 625 int32 defaultServer = settings.GetDefaultServer(); 626 627 status_t status = B_ENTRY_NOT_FOUND; 628 const char* server = settings.GetServer(defaultServer); 629 630 if (server != NULL) 631 status = ntp_update_time(server, errorString, errorCode); 632 633 if (status != B_OK && settings.GetTryAllServers()) { 634 for (int32 index = 0; ; index++) { 635 if (index == defaultServer) 636 index++; 637 638 server = settings.GetServer(index); 639 if (server == NULL) 640 break; 641 642 status = ntp_update_time(server, errorString, errorCode); 643 if (status == B_OK) 644 break; 645 } 646 } 647 648 return status; 649} 650