xref: /haiku/src/preferences/time/NetworkTimeView.cpp (revision 1026b0a1a76dc88927bb8175c470f638dc5464ee)
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 
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 || 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 
193 status_t
194 Settings::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 
219 status_t
220 Settings::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 
238 int32
239 Settings::_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 
250 status_t
251 Settings::_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 
261 NetworkTimeView::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 
272 void
273 NetworkTimeView::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 
400 void
401 NetworkTimeView::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 
414 bool
415 NetworkTimeView::CheckCanRevert()
416 {
417 	return fSettings.SettingsChanged();
418 }
419 
420 
421 void
422 NetworkTimeView::_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 
479 void
480 NetworkTimeView::_UpdateServerList()
481 {
482 	while (fServerListView->RemoveItem(0L) != 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 
495 void
496 NetworkTimeView::_DoneSynchronizing()
497 {
498 	fUpdateThread = -1;
499 	fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again"));
500 	fSynchronizeButton->Message()->what = kMsgSynchronize;
501 }
502 
503 
504 bool
505 NetworkTimeView::_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 
521 status_t
522 update_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 
551 int32
552 update_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 
574 status_t
575 update_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