xref: /haiku/src/preferences/time/NetworkTimeView.cpp (revision aa3083e086e5a929c061c72983e09d916c548a38)
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 
43 Settings::Settings()
44 	:
45 	fMessage(kMsgNetworkTimeSettings)
46 {
47 	ResetToDefaults();
48 	Load();
49 }
50 
51 
52 Settings::~Settings()
53 {
54 	Save();
55 }
56 
57 
58 void
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*
67 Settings::GetServer(int32 index) const
68 {
69 	const char* server;
70 	fMessage.FindString("server", index, &server);
71 	return server;
72 }
73 
74 
75 void
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
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
99 Settings::GetDefaultServer() const
100 {
101 	int32 index;
102 	fMessage.FindInt32("default server", &index);
103 	return index;
104 }
105 
106 
107 void
108 Settings::SetTryAllServers(bool boolean)
109 {
110 	fMessage.ReplaceBool("try all servers", boolean);
111 }
112 
113 
114 bool
115 Settings::GetTryAllServers() const
116 {
117 	bool boolean;
118 	fMessage.FindBool("try all servers", &boolean);
119 	return boolean;
120 }
121 
122 
123 void
124 Settings::SetSynchronizeAtBoot(bool boolean)
125 {
126 	fMessage.ReplaceBool("synchronize at boot", boolean);
127 }
128 
129 
130 bool
131 Settings::GetSynchronizeAtBoot() const
132 {
133 	bool boolean;
134 	fMessage.FindBool("synchronize at boot", &boolean);
135 	return boolean;
136 }
137 
138 
139 void
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
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
165 Settings::Revert()
166 {
167 	fMessage = fOldMessage;
168 }
169 
170 
171 bool
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
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
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
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
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 
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 
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
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:\r\n%s: %s"),
413 						errorString, strerror(errorCode));
414 				} else {
415 					snprintf(buffer, sizeof(buffer),
416 						B_TRANSLATE("The following error occured "
417 							"while synchronizing:\r\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 }
438 
439 
440 void
441 NetworkTimeView::AttachedToWindow()
442 {
443 	fServerTextControl->SetTarget(this);
444 	fServerListView->SetTarget(this);
445 	fAddButton->SetTarget(this);
446 	fAddButton->SetEnabled(false);
447 	fRemoveButton->SetTarget(this);
448 	fResetButton->SetTarget(this);
449 	fTryAllServersCheckBox->SetTarget(this);
450 	fSynchronizeAtBootCheckBox->SetTarget(this);
451 	fSynchronizeButton->SetTarget(this);
452 }
453 
454 
455 bool
456 NetworkTimeView::CheckCanRevert()
457 {
458 	return fSettings.SettingsChanged();
459 }
460 
461 
462 void
463 NetworkTimeView::_InitView()
464 {
465 	fServerTextControl = new BTextControl(NULL, NULL,
466 		new BMessage(kMsgAddServer));
467 	fServerTextControl->SetModificationMessage(new BMessage(kMsgServerEdited));
468 
469 	const float kButtonWidth = fServerTextControl->Frame().Height();
470 
471 	fAddButton = new BButton("add", "+", new BMessage(kMsgAddServer));
472 	fAddButton->SetToolTip(B_TRANSLATE("Add"));
473 	fAddButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth));
474 
475 	fRemoveButton = new BButton("remove", "−", new BMessage(kMsgRemoveServer));
476 	fRemoveButton->SetToolTip(B_TRANSLATE("Remove"));
477 	fRemoveButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth));
478 
479 	fServerListView = new BListView("serverList");
480 	fServerListView->SetExplicitMinSize(BSize(B_SIZE_UNSET, kButtonWidth * 4));
481 	fServerListView->SetSelectionMessage(new BMessage(kMsgSetDefaultServer));
482 	BScrollView* scrollView = new BScrollView("serverScrollView",
483 		fServerListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
484 	_UpdateServerList();
485 
486 	fTryAllServersCheckBox = new BCheckBox("tryAllServers",
487 		B_TRANSLATE("Try all servers"), new BMessage(kMsgTryAllServers));
488 	fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers());
489 
490 	fSynchronizeAtBootCheckBox = new BCheckBox("autoUpdate",
491 		B_TRANSLATE("Synchronize at boot"),
492 		new BMessage(kMsgSynchronizeAtBoot));
493 	fSynchronizeAtBootCheckBox->SetValue(fSettings.GetSynchronizeAtBoot());
494 
495 	fResetButton = new BButton("reset",
496 		B_TRANSLATE("Reset to default server list"),
497 		new BMessage(kMsgResetServerList));
498 
499 	fSynchronizeButton = new BButton("update", B_TRANSLATE("Synchronize"),
500 		new BMessage(kMsgSynchronize));
501 
502 	BLayoutBuilder::Group<>(this, B_VERTICAL)
503 		.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
504 			.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
505 				.Add(fServerTextControl)
506 				.Add(fAddButton)
507 			.End()
508 			.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
509 				.Add(scrollView)
510 				.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
511 					.Add(fRemoveButton)
512 					.AddGlue()
513 				.End()
514 			.End()
515 		.End()
516 		.AddGroup(B_HORIZONTAL)
517 			.AddGroup(B_VERTICAL, 0)
518 				.Add(fTryAllServersCheckBox)
519 				.Add(fSynchronizeAtBootCheckBox)
520 			.End()
521 		.End()
522 		.AddGroup(B_HORIZONTAL)
523 			.AddGlue()
524 			.Add(fResetButton)
525 			.Add(fSynchronizeButton)
526 		.End()
527 		.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
528 			B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING);
529 }
530 
531 
532 void
533 NetworkTimeView::_UpdateServerList()
534 {
535 	BListItem* item;
536 	while ((item = fServerListView->RemoveItem((int32)0)) != NULL)
537 		delete item;
538 
539 	const char* server;
540 	int32 index = 0;
541 	while ((server = fSettings.GetServer(index++)) != NULL)
542 		fServerListView->AddItem(new BStringItem(server));
543 
544 	fServerListView->Select(fSettings.GetDefaultServer());
545 	fServerListView->ScrollToSelection();
546 
547 	fRemoveButton->SetEnabled(fServerListView->CountItems() > 0);
548 }
549 
550 
551 void
552 NetworkTimeView::_DoneSynchronizing()
553 {
554 	fUpdateThread = -1;
555 	fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again"));
556 	fSynchronizeButton->Message()->what = kMsgSynchronize;
557 }
558 
559 
560 bool
561 NetworkTimeView::_IsValidServerName(const char* serverName)
562 {
563 	if (serverName == NULL || *serverName == '\0')
564 		return false;
565 
566 	for (int32 i = 0; serverName[i] != '\0'; i++) {
567 		char c = serverName[i];
568 		// Simple URL validation, no scheme should be present
569 		if (!(isalnum(c) || c == '.' || c == '-' || c == '_'))
570 			return false;
571 	}
572 
573 	return true;
574 }
575 
576 
577 //	#pragma mark - update functions
578 
579 
580 int32
581 update_thread(void* params)
582 {
583 	BList* list = (BList*)params;
584 	BMessenger* messenger = (BMessenger*)list->ItemAt(1);
585 
586 	const char* errorString = NULL;
587 	int32 errorCode = 0;
588 	status_t status = update_time(*(Settings*)list->ItemAt(0),
589 		&errorString, &errorCode);
590 
591 	BMessage result(kMsgSynchronizationResult);
592 	result.AddInt32("status", status);
593 	result.AddString("error string", errorString);
594 	if (errorCode != 0)
595 		result.AddInt32("error code", errorCode);
596 
597 	messenger->SendMessage(&result);
598 	delete messenger;
599 
600 	return B_OK;
601 }
602 
603 
604 status_t
605 update_time(const Settings& settings, BMessenger* messenger,
606 	thread_id* thread)
607 {
608 	BList* params = new BList(2);
609 	params->AddItem((void*)&settings);
610 	params->AddItem((void*)messenger);
611 	*thread = spawn_thread(update_thread, "ntpUpdate", 64, params);
612 
613 	return resume_thread(*thread);
614 }
615 
616 
617 status_t
618 update_time(const Settings& settings, const char** errorString,
619 	int32* errorCode)
620 {
621 	int32 defaultServer = settings.GetDefaultServer();
622 
623 	status_t status = B_ENTRY_NOT_FOUND;
624 	const char* server = settings.GetServer(defaultServer);
625 
626 	if (server != NULL)
627 		status = ntp_update_time(server, errorString, errorCode);
628 
629 	if (status != B_OK && settings.GetTryAllServers()) {
630 		for (int32 index = 0; ; index++) {
631 			if (index == defaultServer)
632 				index++;
633 
634 			server = settings.GetServer(index);
635 			if (server == NULL)
636 				break;
637 
638 			status = ntp_update_time(server, errorString, errorCode);
639 			if (status == B_OK)
640 				break;
641 		}
642 	}
643 
644 	return status;
645 }
646