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:\r\n%s: %s"), 413 errorString, strerror(errorCode)); 414 } else { 415 snprintf(buffer, sizeof(buffer), 416 B_TRANSLATE("The following error occured " 417 "while synchronizing:\r\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 } 438 439 440 void 441 NetworkTimeView::AttachedToWindow() 442 { 443 fServerTextControl->SetTarget(this); 444 fServerListView->SetTarget(this); 445 fAddButton->SetTarget(this); 446 fAddButton->SetEnabled(false); 447 fRemoveButton->SetTarget(this); 448 fResetButton->SetTarget(this); 449 fTryAllServersCheckBox->SetTarget(this); 450 fSynchronizeAtBootCheckBox->SetTarget(this); 451 fSynchronizeButton->SetTarget(this); 452 } 453 454 455 bool 456 NetworkTimeView::CheckCanRevert() 457 { 458 return fSettings.SettingsChanged(); 459 } 460 461 462 void 463 NetworkTimeView::_InitView() 464 { 465 fServerTextControl = new BTextControl(NULL, NULL, 466 new BMessage(kMsgAddServer)); 467 fServerTextControl->SetModificationMessage(new BMessage(kMsgServerEdited)); 468 469 const float kButtonWidth = fServerTextControl->Frame().Height(); 470 471 fAddButton = new BButton("add", "+", new BMessage(kMsgAddServer)); 472 fAddButton->SetToolTip(B_TRANSLATE("Add")); 473 fAddButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth)); 474 475 fRemoveButton = new BButton("remove", "−", new BMessage(kMsgRemoveServer)); 476 fRemoveButton->SetToolTip(B_TRANSLATE("Remove")); 477 fRemoveButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth)); 478 479 fServerListView = new BListView("serverList"); 480 fServerListView->SetExplicitMinSize(BSize(B_SIZE_UNSET, kButtonWidth * 4)); 481 fServerListView->SetSelectionMessage(new BMessage(kMsgSetDefaultServer)); 482 BScrollView* scrollView = new BScrollView("serverScrollView", 483 fServerListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true); 484 _UpdateServerList(); 485 486 fTryAllServersCheckBox = new BCheckBox("tryAllServers", 487 B_TRANSLATE("Try all servers"), new BMessage(kMsgTryAllServers)); 488 fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers()); 489 490 fSynchronizeAtBootCheckBox = new BCheckBox("autoUpdate", 491 B_TRANSLATE("Synchronize at boot"), 492 new BMessage(kMsgSynchronizeAtBoot)); 493 fSynchronizeAtBootCheckBox->SetValue(fSettings.GetSynchronizeAtBoot()); 494 495 fResetButton = new BButton("reset", 496 B_TRANSLATE("Reset to default server list"), 497 new BMessage(kMsgResetServerList)); 498 499 fSynchronizeButton = new BButton("update", B_TRANSLATE("Synchronize"), 500 new BMessage(kMsgSynchronize)); 501 502 BLayoutBuilder::Group<>(this, B_VERTICAL) 503 .AddGroup(B_VERTICAL, B_USE_SMALL_SPACING) 504 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING) 505 .Add(fServerTextControl) 506 .Add(fAddButton) 507 .End() 508 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING) 509 .Add(scrollView) 510 .AddGroup(B_VERTICAL, B_USE_SMALL_SPACING) 511 .Add(fRemoveButton) 512 .AddGlue() 513 .End() 514 .End() 515 .End() 516 .AddGroup(B_HORIZONTAL) 517 .AddGroup(B_VERTICAL, 0) 518 .Add(fTryAllServersCheckBox) 519 .Add(fSynchronizeAtBootCheckBox) 520 .End() 521 .End() 522 .AddGroup(B_HORIZONTAL) 523 .AddGlue() 524 .Add(fResetButton) 525 .Add(fSynchronizeButton) 526 .End() 527 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 528 B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING); 529 } 530 531 532 void 533 NetworkTimeView::_UpdateServerList() 534 { 535 BListItem* item; 536 while ((item = fServerListView->RemoveItem((int32)0)) != NULL) 537 delete item; 538 539 const char* server; 540 int32 index = 0; 541 while ((server = fSettings.GetServer(index++)) != NULL) 542 fServerListView->AddItem(new BStringItem(server)); 543 544 fServerListView->Select(fSettings.GetDefaultServer()); 545 fServerListView->ScrollToSelection(); 546 547 fRemoveButton->SetEnabled(fServerListView->CountItems() > 0); 548 } 549 550 551 void 552 NetworkTimeView::_DoneSynchronizing() 553 { 554 fUpdateThread = -1; 555 fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again")); 556 fSynchronizeButton->Message()->what = kMsgSynchronize; 557 } 558 559 560 bool 561 NetworkTimeView::_IsValidServerName(const char* serverName) 562 { 563 if (serverName == NULL || *serverName == '\0') 564 return false; 565 566 for (int32 i = 0; serverName[i] != '\0'; i++) { 567 char c = serverName[i]; 568 // Simple URL validation, no scheme should be present 569 if (!(isalnum(c) || c == '.' || c == '-' || c == '_')) 570 return false; 571 } 572 573 return true; 574 } 575 576 577 // #pragma mark - update functions 578 579 580 int32 581 update_thread(void* params) 582 { 583 BList* list = (BList*)params; 584 BMessenger* messenger = (BMessenger*)list->ItemAt(1); 585 586 const char* errorString = NULL; 587 int32 errorCode = 0; 588 status_t status = update_time(*(Settings*)list->ItemAt(0), 589 &errorString, &errorCode); 590 591 BMessage result(kMsgSynchronizationResult); 592 result.AddInt32("status", status); 593 result.AddString("error string", errorString); 594 if (errorCode != 0) 595 result.AddInt32("error code", errorCode); 596 597 messenger->SendMessage(&result); 598 delete messenger; 599 600 return B_OK; 601 } 602 603 604 status_t 605 update_time(const Settings& settings, BMessenger* messenger, 606 thread_id* thread) 607 { 608 BList* params = new BList(2); 609 params->AddItem((void*)&settings); 610 params->AddItem((void*)messenger); 611 *thread = spawn_thread(update_thread, "ntpUpdate", 64, params); 612 613 return resume_thread(*thread); 614 } 615 616 617 status_t 618 update_time(const Settings& settings, const char** errorString, 619 int32* errorCode) 620 { 621 int32 defaultServer = settings.GetDefaultServer(); 622 623 status_t status = B_ENTRY_NOT_FOUND; 624 const char* server = settings.GetServer(defaultServer); 625 626 if (server != NULL) 627 status = ntp_update_time(server, errorString, errorCode); 628 629 if (status != B_OK && settings.GetTryAllServers()) { 630 for (int32 index = 0; ; index++) { 631 if (index == defaultServer) 632 index++; 633 634 server = settings.GetServer(index); 635 if (server == NULL) 636 break; 637 638 status = ntp_update_time(server, errorString, errorCode); 639 if (status == B_OK) 640 break; 641 } 642 } 643 644 return status; 645 } 646