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 43 Settings::Settings() 44 : 45 fMessage(kMsgNetworkTimeSettings) 46 { 47 ResetToDefaults(); 48 Load(); 49 } 50 51 52 Settings::~Settings() 53 { 54 Save(); 55 } 56 57 58 void 59 Settings::AddServer(const char* server) 60 { 61 if (_GetStringByValue("server", server) == B_ERROR) 62 fMessage.AddString("server", server); 63 } 64 65 66 const char* 67 Settings::GetServer(int32 index) const 68 { 69 const char* server; 70 fMessage.FindString("server", index, &server); 71 return server; 72 } 73 74 75 void 76 Settings::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 90 void 91 Settings::SetDefaultServer(int32 index) 92 { 93 if (fMessage.ReplaceInt32("default server", index) != B_OK) 94 fMessage.AddInt32("default server", index); 95 } 96 97 98 int32 99 Settings::GetDefaultServer() const 100 { 101 int32 index; 102 fMessage.FindInt32("default server", &index); 103 return index; 104 } 105 106 107 void 108 Settings::SetTryAllServers(bool boolean) 109 { 110 fMessage.ReplaceBool("try all servers", boolean); 111 } 112 113 114 bool 115 Settings::GetTryAllServers() const 116 { 117 bool boolean; 118 fMessage.FindBool("try all servers", &boolean); 119 return boolean; 120 } 121 122 123 void 124 Settings::SetSynchronizeAtBoot(bool boolean) 125 { 126 fMessage.ReplaceBool("synchronize at boot", boolean); 127 } 128 129 130 bool 131 Settings::GetSynchronizeAtBoot() const 132 { 133 bool boolean; 134 fMessage.FindBool("synchronize at boot", &boolean); 135 return boolean; 136 } 137 138 139 void 140 Settings::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 153 void 154 Settings::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 164 void 165 Settings::Revert() 166 { 167 fMessage = fOldMessage; 168 } 169 170 171 bool 172 Settings::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 201 status_t 202 Settings::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 227 status_t 228 Settings::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 246 int32 247 Settings::_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 260 status_t 261 Settings::_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 276 NetworkTimeView::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 297 NetworkTimeView::~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 310 void 311 NetworkTimeView::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 444 void 445 NetworkTimeView::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 459 bool 460 NetworkTimeView::CheckCanRevert() 461 { 462 return fSettings.SettingsChanged(); 463 } 464 465 466 void 467 NetworkTimeView::_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 .AddGlue() 528 .Add(fResetButton) 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 536 void 537 NetworkTimeView::_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 555 void 556 NetworkTimeView::_DoneSynchronizing() 557 { 558 fUpdateThread = -1; 559 fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again")); 560 fSynchronizeButton->Message()->what = kMsgSynchronize; 561 } 562 563 564 bool 565 NetworkTimeView::_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 584 int32 585 update_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 608 status_t 609 update_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 621 status_t 622 update_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