xref: /haiku/src/preferences/time/NetworkTimeView.cpp (revision 922e7ba1f3228e6f28db69b0ded8f86eb32dea17)
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_TRANSLATE_CONTEXT
33 #define B_TRANSLATE_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)
171 		return true;
172 
173 	char* oldBytes = new char[oldSize];
174 	fOldMessage.Flatten(oldBytes, oldSize);
175 	char* newBytes = new char[newSize];
176 	fMessage.Flatten(newBytes, newSize);
177 
178 	int result = memcmp(oldBytes, newBytes, oldSize);
179 
180 	delete[] oldBytes;
181 	delete[] newBytes;
182 
183 	if (result != 0)
184 		return true;
185 	else
186 		return false;
187 }
188 
189 
190 status_t
191 Settings::Load()
192 {
193 	status_t status;
194 
195 	BPath path;
196 	if ((status = _GetPath(path)) != B_OK)
197 		return status;
198 
199 	BFile file(path.Path(), B_READ_ONLY);
200 	if ((status = file.InitCheck()) != B_OK)
201 		return status;
202 
203 	BMessage load;
204 	if ((status = load.Unflatten(&file)) != B_OK)
205 		return status;
206 
207 	if (load.what != kMsgNetworkTimeSettings)
208 		return B_BAD_TYPE;
209 
210 	fMessage = load;
211 	fOldMessage = fMessage;
212 	return B_OK;
213 }
214 
215 
216 status_t
217 Settings::Save()
218 {
219 	status_t status;
220 
221 	BPath path;
222 	if ((status = _GetPath(path)) != B_OK)
223 		return status;
224 
225 	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
226 	if ((status = file.InitCheck()) != B_OK)
227 		return status;
228 
229 	file.SetSize(0);
230 
231 	return fMessage.Flatten(&file);
232 }
233 
234 
235 int32
236 Settings::_GetStringByValue(const char* name, const char* value)
237 {
238 	const char* string;
239 	for (int32 index = 0; fMessage.FindString(
240 		name, index, &string) == B_OK; index++)
241 		if (strcmp(string, value) == 0)
242 			return index;
243 	return B_ERROR;
244 }
245 
246 
247 status_t
248 Settings::_GetPath(BPath& path)
249 {
250 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
251 	if (status != B_OK)
252 		return status;
253 	path.Append("networktime settings");
254 	return B_OK;
255 }
256 
257 
258 NetworkTimeView::NetworkTimeView(const char* name)
259 	:
260 	BGroupView(name, B_VERTICAL, B_USE_DEFAULT_SPACING),
261 	fSettings(),
262 	fUpdateThread(-1)
263 {
264 	fSettings.Load();
265 	_InitView();
266 }
267 
268 
269 void
270 NetworkTimeView::MessageReceived(BMessage* message)
271 {
272 	switch (message->what) {
273 		case kMsgSetDefaultServer:
274 		{
275 			int32 sel = fServerListView->CurrentSelection();
276 			if (sel < 0)
277 				fServerListView->Select(fSettings.GetDefaultServer());
278 			else {
279 				fSettings.SetDefaultServer(sel);
280 				Looper()->PostMessage(new BMessage(kMsgChange));
281 			}
282 			break;
283 		}
284 		case kMsgServerEdited:
285 		{
286 			rgb_color defaultColor = ui_color(B_CONTROL_TEXT_COLOR);
287 			rgb_color red = {255, 0, 0};
288 			int32 length = fServerTextControl->TextView()->TextLength();
289 
290 			if (_IsValidServerName(fServerTextControl->TextView()->Text()))
291 				fServerTextControl->TextView()->SetFontAndColor(0, length, NULL, 0, &defaultColor);
292 			else
293 				fServerTextControl->TextView()->SetFontAndColor(0, length, NULL, 0, &red);
294 
295 			break;
296 		}
297 		case kMsgAddServer:
298 			if (!_IsValidServerName(fServerTextControl->TextView()->Text()))
299 				break;
300 
301 			fSettings.AddServer(fServerTextControl->Text());
302 			_UpdateServerList();
303 			fServerTextControl->SetText("");
304 			Looper()->PostMessage(new BMessage(kMsgChange));
305 			break;
306 
307 		case kMsgRemoveServer:
308 			fSettings.RemoveServer(((BStringItem*)
309 				fServerListView->ItemAt(
310 					fServerListView->
311 					CurrentSelection()))->Text());
312 			_UpdateServerList();
313 			Looper()->PostMessage(new BMessage(kMsgChange));
314 			break;
315 
316 		case kMsgResetServerList:
317 			fSettings.ResetServersToDefaults();
318 			_UpdateServerList();
319 			Looper()->PostMessage(new BMessage(kMsgChange));
320 			break;
321 
322 		case kMsgTryAllServers:
323 			fSettings.SetTryAllServers(
324 				fTryAllServersCheckBox->Value());
325 			Looper()->PostMessage(new BMessage(kMsgChange));
326 			break;
327 
328 		case kMsgSynchronizeAtBoot:
329 			fSettings.SetSynchronizeAtBoot(
330 				fSynchronizeAtBootCheckBox->Value());
331 			Looper()->PostMessage(new BMessage(kMsgChange));
332 			break;
333 
334 		case kMsgStopSynchronization:
335 			if (fUpdateThread >= B_OK)
336 				kill_thread(fUpdateThread);
337 			_DoneSynchronizing();
338 			break;
339 
340 		case kMsgSynchronize:
341 		{
342 			if (fUpdateThread >= B_OK)
343 				break;
344 
345 			BMessenger* messenger = new BMessenger(this);
346 			update_time(fSettings, messenger, &fUpdateThread);
347 			fSynchronizeButton->SetLabel(B_TRANSLATE("Stop"));
348 			fSynchronizeButton->Message()->what = kMsgStopSynchronization;
349 			break;
350 		}
351 
352 		case kMsgSynchronizationResult:
353 		{
354 			_DoneSynchronizing();
355 
356 			status_t status;
357 			if (message->FindInt32("status", (int32 *)&status) == B_OK) {
358 				if (status == B_OK) return;
359 
360 				const char* errorString;
361 				message->FindString("error string", &errorString);
362 				char buffer[256];
363 
364 				int32 errorCode;
365 				if (message->FindInt32("error code", &errorCode)
366 					== B_OK)
367 					snprintf(buffer, sizeof(buffer),
368 						B_TRANSLATE("The following error occured "
369 						"while synchronizing:\r\n%s: %s"),
370 						errorString, strerror(errorCode));
371 				else
372 					snprintf(buffer, sizeof(buffer),
373 						B_TRANSLATE("The following error occured "
374 						"while synchronizing:\r\n%s"),
375 						errorString);
376 
377 				(new BAlert(B_TRANSLATE("Time"), buffer,
378 					B_TRANSLATE("OK")))->Go();
379 			}
380 			break;
381 		}
382 
383 		case kMsgRevert:
384 			fSettings.Revert();
385 		    fTryAllServersCheckBox->SetValue(
386 		    	fSettings.GetTryAllServers());
387 			fSynchronizeAtBootCheckBox->SetValue(
388 				fSettings.GetSynchronizeAtBoot());
389 			_UpdateServerList();
390 			break;
391 	}
392 }
393 
394 
395 void
396 NetworkTimeView::AttachedToWindow()
397 {
398 	fServerTextControl->SetTarget(this);
399 	fServerListView->SetTarget(this);
400 	fAddButton->SetTarget(this);
401 	fRemoveButton->SetTarget(this);
402 	fResetButton->SetTarget(this);
403 	fTryAllServersCheckBox->SetTarget(this);
404 	fSynchronizeAtBootCheckBox->SetTarget(this);
405 	fSynchronizeButton->SetTarget(this);
406 }
407 
408 
409 bool
410 NetworkTimeView::CheckCanRevert()
411 {
412 	return fSettings.SettingsChanged();
413 }
414 
415 
416 void
417 NetworkTimeView::_InitView()
418 {
419 	fServerTextControl = new BTextControl(NULL, NULL, new BMessage(kMsgAddServer));
420 	fServerTextControl->SetModificationMessage(new BMessage(kMsgServerEdited));
421 
422 	fAddButton = new BButton("add", B_TRANSLATE("Add"),
423 		new BMessage(kMsgAddServer));
424 	fRemoveButton = new BButton("remove", B_TRANSLATE("Remove"),
425 		new BMessage(kMsgRemoveServer));
426 	fResetButton = new BButton("reset", B_TRANSLATE("Reset"),
427 		new BMessage(kMsgResetServerList));
428 
429 	fServerListView = new BListView("serverList");
430 	fServerListView->SetSelectionMessage(new
431 		BMessage(kMsgSetDefaultServer));
432 	BScrollView* scrollView = new BScrollView("serverScrollView",
433 		fServerListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
434 	_UpdateServerList();
435 
436 	fTryAllServersCheckBox = new BCheckBox("tryAllServers",
437 		B_TRANSLATE("Try all servers"), new BMessage(kMsgTryAllServers));
438 	fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers());
439 
440 	fSynchronizeAtBootCheckBox = new BCheckBox("autoUpdate",
441 		B_TRANSLATE("Synchronize at boot"),
442 		new BMessage(kMsgSynchronizeAtBoot));
443 	fSynchronizeAtBootCheckBox->SetValue(fSettings.GetSynchronizeAtBoot());
444 	fSynchronizeButton = new BButton("update", B_TRANSLATE("Synchronize"),
445 		new BMessage(kMsgSynchronize));
446 	fSynchronizeButton->SetExplicitAlignment(
447 		BAlignment(B_ALIGN_RIGHT, B_ALIGN_BOTTOM));
448 
449 	const float kInset = be_control_look->DefaultItemSpacing();
450 	BLayoutBuilder::Group<>(this)
451 		.AddGroup(B_HORIZONTAL)
452 			.AddGroup(B_VERTICAL, 0)
453 				.Add(fServerTextControl)
454 				.Add(scrollView)
455 			.End()
456 			.AddGroup(B_VERTICAL, kInset / 2)
457 				.Add(fAddButton)
458 				.Add(fRemoveButton)
459 				.Add(fResetButton)
460 				.AddGlue()
461 			.End()
462 		.End()
463 		.AddGroup(B_HORIZONTAL)
464 			.AddGroup(B_VERTICAL, 0)
465 				.Add(fTryAllServersCheckBox)
466 				.Add(fSynchronizeAtBootCheckBox)
467 			.End()
468 			.Add(fSynchronizeButton)
469 		.End()
470 		.SetInsets(kInset, kInset, kInset, kInset);
471 }
472 
473 
474 void
475 NetworkTimeView::_UpdateServerList()
476 {
477 	while (fServerListView->RemoveItem(0L) != NULL);
478 
479 	const char* server;
480 	int32 index = 0;
481 
482 	while ((server = fSettings.GetServer(index++)) != NULL)
483 		fServerListView->AddItem(new BStringItem(server));
484 
485 	fServerListView->Select(fSettings.GetDefaultServer());
486 	fServerListView->ScrollToSelection();
487 }
488 
489 
490 void
491 NetworkTimeView::_DoneSynchronizing()
492 {
493 	fUpdateThread = -1;
494 	fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again"));
495 	fSynchronizeButton->Message()->what = kMsgSynchronize;
496 }
497 
498 
499 bool
500 NetworkTimeView::_IsValidServerName(const char * serverName)
501 {
502 	if (serverName[0] == '\0')
503 		return false;
504 
505 	for (int32 i = 0; serverName[i] != '\0'; i++) {
506 		char c = serverName[i];
507 		// Simple URL validation, no scheme should be present
508 		if (!(isalnum(c) || c == '.' || c == '-' || c == '_'))
509 			return false;
510 	}
511 
512 	return true;
513 }
514 
515 
516 status_t
517 update_time(const Settings& settings, const char** errorString,
518 	int32* errorCode)
519 {
520 	int32 defaultServer = settings.GetDefaultServer();
521 
522 	status_t status = B_ENTRY_NOT_FOUND;
523 	const char* server = settings.GetServer(defaultServer);
524 
525 	if (server != NULL)
526 		status = ntp_update_time(server, errorString, errorCode);
527 
528 	if (status != B_OK && settings.GetTryAllServers()) {
529 		for (int32 index = 0; ; index++) {
530 			if (index == defaultServer)
531 				index++;
532 			server = settings.GetServer(index);
533 			if (server == NULL)
534 				break;
535 
536 			status = ntp_update_time(server, errorString, errorCode);
537 			if (status == B_OK)
538 				break;
539 		}
540 	}
541 
542 	return status;
543 }
544 
545 
546 int32
547 update_thread(void* params)
548 {
549 	BList* list = (BList*)params;
550 	BMessenger* messenger = (BMessenger*)list->ItemAt(1);
551 
552 	const char* errorString = NULL;
553 	int32 errorCode = 0;
554 	status_t status = update_time(*(Settings*)list->ItemAt(0),
555 		&errorString, &errorCode);
556 
557 	BMessage result(kMsgSynchronizationResult);
558 	result.AddInt32("status", status);
559 	result.AddString("error string", errorString);
560 	if (errorCode != 0)
561 		result.AddInt32("error code", errorCode);
562 	messenger->SendMessage(&result);
563 
564 	delete messenger;
565 	return B_OK;
566 }
567 
568 
569 status_t
570 update_time(const Settings& settings, BMessenger* messenger,
571 	thread_id* thread)
572 {
573 	BList* params = new BList(2);
574 	params->AddItem((void*)&settings);
575 	params->AddItem((void*)messenger);
576 	*thread = spawn_thread(update_thread, "ntpUpdate", 64, params);
577 	return resume_thread(*thread);
578 }
579 
580