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
Settings()43 Settings::Settings()
44 :
45 fMessage(kMsgNetworkTimeSettings)
46 {
47 ResetToDefaults();
48 Load();
49 }
50
51
~Settings()52 Settings::~Settings()
53 {
54 Save();
55 }
56
57
58 void
AddServer(const char * server)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*
GetServer(int32 index) const67 Settings::GetServer(int32 index) const
68 {
69 const char* server;
70 fMessage.FindString("server", index, &server);
71 return server;
72 }
73
74
75 void
RemoveServer(const char * server)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
SetDefaultServer(int32 index)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
GetDefaultServer() const99 Settings::GetDefaultServer() const
100 {
101 int32 index;
102 fMessage.FindInt32("default server", &index);
103 return index;
104 }
105
106
107 void
SetTryAllServers(bool boolean)108 Settings::SetTryAllServers(bool boolean)
109 {
110 fMessage.ReplaceBool("try all servers", boolean);
111 }
112
113
114 bool
GetTryAllServers() const115 Settings::GetTryAllServers() const
116 {
117 bool boolean;
118 fMessage.FindBool("try all servers", &boolean);
119 return boolean;
120 }
121
122
123 void
SetSynchronizeAtBoot(bool boolean)124 Settings::SetSynchronizeAtBoot(bool boolean)
125 {
126 fMessage.ReplaceBool("synchronize at boot", boolean);
127 }
128
129
130 bool
GetSynchronizeAtBoot() const131 Settings::GetSynchronizeAtBoot() const
132 {
133 bool boolean;
134 fMessage.FindBool("synchronize at boot", &boolean);
135 return boolean;
136 }
137
138
139 void
ResetServersToDefaults()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
ResetToDefaults()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
Revert()165 Settings::Revert()
166 {
167 fMessage = fOldMessage;
168 }
169
170
171 bool
SettingsChanged()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
Load()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
Save()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
_GetStringByValue(const char * name,const char * value)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
_GetPath(BPath & path)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
NetworkTimeView(const char * name)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
~NetworkTimeView()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
MessageReceived(BMessage * message)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
AttachedToWindow()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
CheckCanRevert()460 NetworkTimeView::CheckCanRevert()
461 {
462 return fSettings.SettingsChanged();
463 }
464
465
466 void
_InitView()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 .Add(fResetButton)
528 .AddGlue()
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
_UpdateServerList()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
_DoneSynchronizing()556 NetworkTimeView::_DoneSynchronizing()
557 {
558 fUpdateThread = -1;
559 fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again"));
560 fSynchronizeButton->Message()->what = kMsgSynchronize;
561 }
562
563
564 bool
_IsValidServerName(const char * serverName)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
update_thread(void * params)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
update_time(const Settings & settings,BMessenger * messenger,thread_id * thread)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
update_time(const Settings & settings,const char ** errorString,int32 * errorCode)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