xref: /haiku/src/preferences/time/NetworkTimeView.cpp (revision 307745c6ad187448a37a292e269402e65de46be7)
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