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