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 36Settings::Settings() 37 : 38 fMessage(kMsgNetworkTimeSettings) 39{ 40 ResetToDefaults(); 41 Load(); 42} 43 44 45Settings::~Settings() 46{ 47 Save(); 48} 49 50 51void 52Settings::AddServer(const char* server) 53{ 54 if (_GetStringByValue("server", server) == B_ERROR) 55 fMessage.AddString("server", server); 56} 57 58 59const char* 60Settings::GetServer(int32 index) const 61{ 62 const char* server; 63 fMessage.FindString("server", index, &server); 64 return server; 65} 66 67 68void 69Settings::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 83void 84Settings::SetDefaultServer(int32 index) 85{ 86 if (fMessage.ReplaceInt32("default server", index) != B_OK) 87 fMessage.AddInt32("default server", index); 88} 89 90 91int32 92Settings::GetDefaultServer() const 93{ 94 int32 index; 95 fMessage.FindInt32("default server", &index); 96 return index; 97} 98 99 100void 101Settings::SetTryAllServers(bool boolean) 102{ 103 fMessage.ReplaceBool("try all servers", boolean); 104} 105 106 107bool 108Settings::GetTryAllServers() const 109{ 110 bool boolean; 111 fMessage.FindBool("try all servers", &boolean); 112 return boolean; 113} 114 115 116void 117Settings::SetSynchronizeAtBoot(bool boolean) 118{ 119 fMessage.ReplaceBool("synchronize at boot", boolean); 120} 121 122 123bool 124Settings::GetSynchronizeAtBoot() const 125{ 126 bool boolean; 127 fMessage.FindBool("synchronize at boot", &boolean); 128 return boolean; 129} 130 131 132void 133Settings::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 146void 147Settings::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 157void 158Settings::Revert() 159{ 160 fMessage = fOldMessage; 161} 162 163 164bool 165Settings::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 193status_t 194Settings::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 219status_t 220Settings::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 238int32 239Settings::_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 250status_t 251Settings::_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 261NetworkTimeView::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 272void 273NetworkTimeView::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 400void 401NetworkTimeView::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 414bool 415NetworkTimeView::CheckCanRevert() 416{ 417 return fSettings.SettingsChanged(); 418} 419 420 421void 422NetworkTimeView::_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 479void 480NetworkTimeView::_UpdateServerList() 481{ 482 while (fServerListView->RemoveItem((int32)0) != 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 495void 496NetworkTimeView::_DoneSynchronizing() 497{ 498 fUpdateThread = -1; 499 fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again")); 500 fSynchronizeButton->Message()->what = kMsgSynchronize; 501} 502 503 504bool 505NetworkTimeView::_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 521status_t 522update_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 551int32 552update_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 574status_t 575update_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