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_TRANSLATE_CONTEXT 33 #define B_TRANSLATE_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) 171 return true; 172 173 char* oldBytes = new char[oldSize]; 174 fOldMessage.Flatten(oldBytes, oldSize); 175 char* newBytes = new char[newSize]; 176 fMessage.Flatten(newBytes, newSize); 177 178 int result = memcmp(oldBytes, newBytes, oldSize); 179 180 delete[] oldBytes; 181 delete[] newBytes; 182 183 if (result != 0) 184 return true; 185 else 186 return false; 187 } 188 189 190 status_t 191 Settings::Load() 192 { 193 status_t status; 194 195 BPath path; 196 if ((status = _GetPath(path)) != B_OK) 197 return status; 198 199 BFile file(path.Path(), B_READ_ONLY); 200 if ((status = file.InitCheck()) != B_OK) 201 return status; 202 203 BMessage load; 204 if ((status = load.Unflatten(&file)) != B_OK) 205 return status; 206 207 if (load.what != kMsgNetworkTimeSettings) 208 return B_BAD_TYPE; 209 210 fMessage = load; 211 fOldMessage = fMessage; 212 return B_OK; 213 } 214 215 216 status_t 217 Settings::Save() 218 { 219 status_t status; 220 221 BPath path; 222 if ((status = _GetPath(path)) != B_OK) 223 return status; 224 225 BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 226 if ((status = file.InitCheck()) != B_OK) 227 return status; 228 229 file.SetSize(0); 230 231 return fMessage.Flatten(&file); 232 } 233 234 235 int32 236 Settings::_GetStringByValue(const char* name, const char* value) 237 { 238 const char* string; 239 for (int32 index = 0; fMessage.FindString( 240 name, index, &string) == B_OK; index++) 241 if (strcmp(string, value) == 0) 242 return index; 243 return B_ERROR; 244 } 245 246 247 status_t 248 Settings::_GetPath(BPath& path) 249 { 250 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 251 if (status != B_OK) 252 return status; 253 path.Append("networktime settings"); 254 return B_OK; 255 } 256 257 258 NetworkTimeView::NetworkTimeView(const char* name) 259 : 260 BGroupView(name, B_VERTICAL, B_USE_DEFAULT_SPACING), 261 fSettings(), 262 fUpdateThread(-1) 263 { 264 fSettings.Load(); 265 _InitView(); 266 } 267 268 269 void 270 NetworkTimeView::MessageReceived(BMessage* message) 271 { 272 switch (message->what) { 273 case kMsgSetDefaultServer: 274 { 275 int32 sel = fServerListView->CurrentSelection(); 276 if (sel < 0) 277 fServerListView->Select(fSettings.GetDefaultServer()); 278 else { 279 fSettings.SetDefaultServer(sel); 280 Looper()->PostMessage(new BMessage(kMsgChange)); 281 } 282 break; 283 } 284 case kMsgServerEdited: 285 { 286 rgb_color defaultColor = ui_color(B_CONTROL_TEXT_COLOR); 287 rgb_color red = {255, 0, 0}; 288 int32 length = fServerTextControl->TextView()->TextLength(); 289 290 if (_IsValidServerName(fServerTextControl->TextView()->Text())) 291 fServerTextControl->TextView()->SetFontAndColor(0, length, NULL, 0, &defaultColor); 292 else 293 fServerTextControl->TextView()->SetFontAndColor(0, length, NULL, 0, &red); 294 295 break; 296 } 297 case kMsgAddServer: 298 if (!_IsValidServerName(fServerTextControl->TextView()->Text())) 299 break; 300 301 fSettings.AddServer(fServerTextControl->Text()); 302 _UpdateServerList(); 303 fServerTextControl->SetText(""); 304 Looper()->PostMessage(new BMessage(kMsgChange)); 305 break; 306 307 case kMsgRemoveServer: 308 fSettings.RemoveServer(((BStringItem*) 309 fServerListView->ItemAt( 310 fServerListView-> 311 CurrentSelection()))->Text()); 312 _UpdateServerList(); 313 Looper()->PostMessage(new BMessage(kMsgChange)); 314 break; 315 316 case kMsgResetServerList: 317 fSettings.ResetServersToDefaults(); 318 _UpdateServerList(); 319 Looper()->PostMessage(new BMessage(kMsgChange)); 320 break; 321 322 case kMsgTryAllServers: 323 fSettings.SetTryAllServers( 324 fTryAllServersCheckBox->Value()); 325 Looper()->PostMessage(new BMessage(kMsgChange)); 326 break; 327 328 case kMsgSynchronizeAtBoot: 329 fSettings.SetSynchronizeAtBoot( 330 fSynchronizeAtBootCheckBox->Value()); 331 Looper()->PostMessage(new BMessage(kMsgChange)); 332 break; 333 334 case kMsgStopSynchronization: 335 if (fUpdateThread >= B_OK) 336 kill_thread(fUpdateThread); 337 _DoneSynchronizing(); 338 break; 339 340 case kMsgSynchronize: 341 { 342 if (fUpdateThread >= B_OK) 343 break; 344 345 BMessenger* messenger = new BMessenger(this); 346 update_time(fSettings, messenger, &fUpdateThread); 347 fSynchronizeButton->SetLabel(B_TRANSLATE("Stop")); 348 fSynchronizeButton->Message()->what = kMsgStopSynchronization; 349 break; 350 } 351 352 case kMsgSynchronizationResult: 353 { 354 _DoneSynchronizing(); 355 356 status_t status; 357 if (message->FindInt32("status", (int32 *)&status) == B_OK) { 358 if (status == B_OK) return; 359 360 const char* errorString; 361 message->FindString("error string", &errorString); 362 char buffer[256]; 363 364 int32 errorCode; 365 if (message->FindInt32("error code", &errorCode) 366 == B_OK) 367 snprintf(buffer, sizeof(buffer), 368 B_TRANSLATE("The following error occured " 369 "while synchronizing:\r\n%s: %s"), 370 errorString, strerror(errorCode)); 371 else 372 snprintf(buffer, sizeof(buffer), 373 B_TRANSLATE("The following error occured " 374 "while synchronizing:\r\n%s"), 375 errorString); 376 377 (new BAlert(B_TRANSLATE("Time"), buffer, 378 B_TRANSLATE("OK")))->Go(); 379 } 380 break; 381 } 382 383 case kMsgRevert: 384 fSettings.Revert(); 385 fTryAllServersCheckBox->SetValue( 386 fSettings.GetTryAllServers()); 387 fSynchronizeAtBootCheckBox->SetValue( 388 fSettings.GetSynchronizeAtBoot()); 389 _UpdateServerList(); 390 break; 391 } 392 } 393 394 395 void 396 NetworkTimeView::AttachedToWindow() 397 { 398 fServerTextControl->SetTarget(this); 399 fServerListView->SetTarget(this); 400 fAddButton->SetTarget(this); 401 fRemoveButton->SetTarget(this); 402 fResetButton->SetTarget(this); 403 fTryAllServersCheckBox->SetTarget(this); 404 fSynchronizeAtBootCheckBox->SetTarget(this); 405 fSynchronizeButton->SetTarget(this); 406 } 407 408 409 bool 410 NetworkTimeView::CheckCanRevert() 411 { 412 return fSettings.SettingsChanged(); 413 } 414 415 416 void 417 NetworkTimeView::_InitView() 418 { 419 fServerTextControl = new BTextControl(NULL, NULL, new BMessage(kMsgAddServer)); 420 fServerTextControl->SetModificationMessage(new BMessage(kMsgServerEdited)); 421 422 fAddButton = new BButton("add", B_TRANSLATE("Add"), 423 new BMessage(kMsgAddServer)); 424 fRemoveButton = new BButton("remove", B_TRANSLATE("Remove"), 425 new BMessage(kMsgRemoveServer)); 426 fResetButton = new BButton("reset", B_TRANSLATE("Reset"), 427 new BMessage(kMsgResetServerList)); 428 429 fServerListView = new BListView("serverList"); 430 fServerListView->SetSelectionMessage(new 431 BMessage(kMsgSetDefaultServer)); 432 BScrollView* scrollView = new BScrollView("serverScrollView", 433 fServerListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true); 434 _UpdateServerList(); 435 436 fTryAllServersCheckBox = new BCheckBox("tryAllServers", 437 B_TRANSLATE("Try all servers"), new BMessage(kMsgTryAllServers)); 438 fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers()); 439 440 fSynchronizeAtBootCheckBox = new BCheckBox("autoUpdate", 441 B_TRANSLATE("Synchronize at boot"), 442 new BMessage(kMsgSynchronizeAtBoot)); 443 fSynchronizeAtBootCheckBox->SetValue(fSettings.GetSynchronizeAtBoot()); 444 fSynchronizeButton = new BButton("update", B_TRANSLATE("Synchronize"), 445 new BMessage(kMsgSynchronize)); 446 fSynchronizeButton->SetExplicitAlignment( 447 BAlignment(B_ALIGN_RIGHT, B_ALIGN_BOTTOM)); 448 449 const float kInset = be_control_look->DefaultItemSpacing(); 450 BLayoutBuilder::Group<>(this) 451 .AddGroup(B_HORIZONTAL) 452 .AddGroup(B_VERTICAL, 0) 453 .Add(fServerTextControl) 454 .Add(scrollView) 455 .End() 456 .AddGroup(B_VERTICAL, kInset / 2) 457 .Add(fAddButton) 458 .Add(fRemoveButton) 459 .Add(fResetButton) 460 .AddGlue() 461 .End() 462 .End() 463 .AddGroup(B_HORIZONTAL) 464 .AddGroup(B_VERTICAL, 0) 465 .Add(fTryAllServersCheckBox) 466 .Add(fSynchronizeAtBootCheckBox) 467 .End() 468 .Add(fSynchronizeButton) 469 .End() 470 .SetInsets(kInset, kInset, kInset, kInset); 471 } 472 473 474 void 475 NetworkTimeView::_UpdateServerList() 476 { 477 while (fServerListView->RemoveItem(0L) != NULL); 478 479 const char* server; 480 int32 index = 0; 481 482 while ((server = fSettings.GetServer(index++)) != NULL) 483 fServerListView->AddItem(new BStringItem(server)); 484 485 fServerListView->Select(fSettings.GetDefaultServer()); 486 fServerListView->ScrollToSelection(); 487 } 488 489 490 void 491 NetworkTimeView::_DoneSynchronizing() 492 { 493 fUpdateThread = -1; 494 fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again")); 495 fSynchronizeButton->Message()->what = kMsgSynchronize; 496 } 497 498 499 bool 500 NetworkTimeView::_IsValidServerName(const char * serverName) 501 { 502 if (serverName[0] == '\0') 503 return false; 504 505 for (int32 i = 0; serverName[i] != '\0'; i++) { 506 char c = serverName[i]; 507 // Simple URL validation, no scheme should be present 508 if (!(isalnum(c) || c == '.' || c == '-' || c == '_')) 509 return false; 510 } 511 512 return true; 513 } 514 515 516 status_t 517 update_time(const Settings& settings, const char** errorString, 518 int32* errorCode) 519 { 520 int32 defaultServer = settings.GetDefaultServer(); 521 522 status_t status = B_ENTRY_NOT_FOUND; 523 const char* server = settings.GetServer(defaultServer); 524 525 if (server != NULL) 526 status = ntp_update_time(server, errorString, errorCode); 527 528 if (status != B_OK && settings.GetTryAllServers()) { 529 for (int32 index = 0; ; index++) { 530 if (index == defaultServer) 531 index++; 532 server = settings.GetServer(index); 533 if (server == NULL) 534 break; 535 536 status = ntp_update_time(server, errorString, errorCode); 537 if (status == B_OK) 538 break; 539 } 540 } 541 542 return status; 543 } 544 545 546 int32 547 update_thread(void* params) 548 { 549 BList* list = (BList*)params; 550 BMessenger* messenger = (BMessenger*)list->ItemAt(1); 551 552 const char* errorString = NULL; 553 int32 errorCode = 0; 554 status_t status = update_time(*(Settings*)list->ItemAt(0), 555 &errorString, &errorCode); 556 557 BMessage result(kMsgSynchronizationResult); 558 result.AddInt32("status", status); 559 result.AddString("error string", errorString); 560 if (errorCode != 0) 561 result.AddInt32("error code", errorCode); 562 messenger->SendMessage(&result); 563 564 delete messenger; 565 return B_OK; 566 } 567 568 569 status_t 570 update_time(const Settings& settings, BMessenger* messenger, 571 thread_id* thread) 572 { 573 BList* params = new BList(2); 574 params->AddItem((void*)&settings); 575 params->AddItem((void*)messenger); 576 *thread = spawn_thread(update_thread, "ntpUpdate", 64, params); 577 return resume_thread(*thread); 578 } 579 580