/*
* Copyright 2011-2014 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
* Hamish Morrison, hamish@lavabit.com
* John Scipione, jscipione@gmail.com
*/
#include "NetworkTimeView.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <Alert.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <File.h>
#include <FindDirectory.h>
#include <Invoker.h>
#include <ListItem.h>
#include <ListView.h>
#include <Path.h>
#include <ScrollView.h>
#include <Size.h>
#include <TextControl.h>
#include "ntp.h"
#include "TimeMessages.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Time"
// #pragma mark - Settings
Settings::Settings()
:
fMessage(kMsgNetworkTimeSettings)
{
ResetToDefaults();
Load();
}
Settings::~Settings()
{
Save();
}
void
Settings::AddServer(const char* server)
{
if (_GetStringByValue("server", server) == B_ERROR)
fMessage.AddString("server", server);
}
const char*
Settings::GetServer(int32 index) const
{
const char* server;
fMessage.FindString("server", index, &server);
return server;
}
void
Settings::RemoveServer(const char* server)
{
int32 index = _GetStringByValue("server", server);
if (index != B_ERROR) {
fMessage.RemoveData("server", index);
int32 count;
fMessage.GetInfo("server", NULL, &count);
if (GetDefaultServer() >= count)
SetDefaultServer(count - 1);
}
}
void
Settings::SetDefaultServer(int32 index)
{
if (fMessage.ReplaceInt32("default server", index) != B_OK)
fMessage.AddInt32("default server", index);
}
int32
Settings::GetDefaultServer() const
{
int32 index;
fMessage.FindInt32("default server", &index);
return index;
}
void
Settings::SetTryAllServers(bool boolean)
{
fMessage.ReplaceBool("try all servers", boolean);
}
bool
Settings::GetTryAllServers() const
{
bool boolean;
fMessage.FindBool("try all servers", &boolean);
return boolean;
}
void
Settings::SetSynchronizeAtBoot(bool boolean)
{
fMessage.ReplaceBool("synchronize at boot", boolean);
}
bool
Settings::GetSynchronizeAtBoot() const
{
bool boolean;
fMessage.FindBool("synchronize at boot", &boolean);
return boolean;
}
void
Settings::ResetServersToDefaults()
{
fMessage.RemoveName("server");
fMessage.AddString("server", "pool.ntp.org");
fMessage.AddString("server", "de.pool.ntp.org");
fMessage.AddString("server", "time.nist.gov");
if (fMessage.ReplaceInt32("default server", 0) != B_OK)
fMessage.AddInt32("default server", 0);
}
void
Settings::ResetToDefaults()
{
fMessage.MakeEmpty();
ResetServersToDefaults();
fMessage.AddBool("synchronize at boot", true);
fMessage.AddBool("try all servers", true);
}
void
Settings::Revert()
{
fMessage = fOldMessage;
}
bool
Settings::SettingsChanged()
{
ssize_t oldSize = fOldMessage.FlattenedSize();
ssize_t newSize = fMessage.FlattenedSize();
if (oldSize != newSize || oldSize < 0 || newSize < 0)
return true;
char* oldBytes = new (std::nothrow) char[oldSize];
if (oldBytes == NULL)
return true;
fOldMessage.Flatten(oldBytes, oldSize);
char* newBytes = new (std::nothrow) char[newSize];
if (newBytes == NULL) {
delete[] oldBytes;
return true;
}
fMessage.Flatten(newBytes, newSize);
int result = memcmp(oldBytes, newBytes, oldSize);
delete[] oldBytes;
delete[] newBytes;
return result != 0;
}
status_t
Settings::Load()
{
status_t status;
BPath path;
if ((status = _GetPath(path)) != B_OK)
return status;
BFile file(path.Path(), B_READ_ONLY);
if ((status = file.InitCheck()) != B_OK)
return status;
BMessage load;
if ((status = load.Unflatten(&file)) != B_OK)
return status;
if (load.what != kMsgNetworkTimeSettings)
return B_BAD_TYPE;
fMessage = load;
fOldMessage = fMessage;
return B_OK;
}
status_t
Settings::Save()
{
status_t status;
BPath path;
if ((status = _GetPath(path)) != B_OK)
return status;
BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
if ((status = file.InitCheck()) != B_OK)
return status;
file.SetSize(0);
return fMessage.Flatten(&file);
}
int32
Settings::_GetStringByValue(const char* name, const char* value)
{
const char* string;
for (int32 index = 0; fMessage.FindString(name, index, &string) == B_OK;
index++) {
if (strcmp(string, value) == 0)
return index;
}
return B_ERROR;
}
status_t
Settings::_GetPath(BPath& path)
{
status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
if (status != B_OK)
return status;
path.Append("networktime settings");
return B_OK;
}
// #pragma mark - NetworkTimeView
NetworkTimeView::NetworkTimeView(const char* name)
:
BGroupView(name, B_VERTICAL, B_USE_DEFAULT_SPACING),
fSettings(),
fServerTextControl(NULL),
fAddButton(NULL),
fRemoveButton(NULL),
fResetButton(NULL),
fServerListView(NULL),
fTryAllServersCheckBox(NULL),
fSynchronizeAtBootCheckBox(NULL),
fSynchronizeButton(NULL),
fTextColor(ui_color(B_CONTROL_TEXT_COLOR)),
fInvalidColor(ui_color(B_FAILURE_COLOR)),
fUpdateThread(-1)
{
fSettings.Load();
_InitView();
}
NetworkTimeView::~NetworkTimeView()
{
delete fServerTextControl;
delete fAddButton;
delete fRemoveButton;
delete fResetButton;
delete fServerListView;
delete fTryAllServersCheckBox;
delete fSynchronizeAtBootCheckBox;
delete fSynchronizeButton;
}
void
NetworkTimeView::MessageReceived(BMessage* message)
{
switch (message->what) {
case kMsgSetDefaultServer:
{
int32 currentSelection = fServerListView->CurrentSelection();
if (currentSelection < 0)
fServerListView->Select(fSettings.GetDefaultServer());
else {
fSettings.SetDefaultServer(currentSelection);
Looper()->PostMessage(new BMessage(kMsgChange));
}
break;
}
case kMsgServerEdited:
{
bool isValid = _IsValidServerName(fServerTextControl->Text());
fServerTextControl->TextView()->SetFontAndColor(0,
fServerTextControl->TextView()->TextLength(), NULL, 0,
isValid ? &fTextColor : &fInvalidColor);
fAddButton->SetEnabled(isValid);
break;
}
case kMsgAddServer:
if (!_IsValidServerName(fServerTextControl->Text()))
break;
fSettings.AddServer(fServerTextControl->Text());
_UpdateServerList();
fServerTextControl->SetText("");
Looper()->PostMessage(new BMessage(kMsgChange));
break;
case kMsgRemoveServer:
{
int32 currentSelection = fServerListView->CurrentSelection();
if (currentSelection < 0)
break;
fSettings.RemoveServer(((BStringItem*)
fServerListView->ItemAt(currentSelection))->Text());
_UpdateServerList();
Looper()->PostMessage(new BMessage(kMsgChange));
break;
}
case kMsgResetServerList:
fSettings.ResetServersToDefaults();
_UpdateServerList();
Looper()->PostMessage(new BMessage(kMsgChange));
break;
case kMsgTryAllServers:
fSettings.SetTryAllServers(
fTryAllServersCheckBox->Value());
Looper()->PostMessage(new BMessage(kMsgChange));
break;
case kMsgSynchronizeAtBoot:
fSettings.SetSynchronizeAtBoot(fSynchronizeAtBootCheckBox->Value());
Looper()->PostMessage(new BMessage(kMsgChange));
break;
case kMsgStopSynchronization:
if (fUpdateThread >= B_OK)
kill_thread(fUpdateThread);
_DoneSynchronizing();
break;
case kMsgSynchronize:
{
if (fUpdateThread >= B_OK)
break;
BMessenger* messenger = new BMessenger(this);
update_time(fSettings, messenger, &fUpdateThread);
fSynchronizeButton->SetLabel(B_TRANSLATE("Stop"));
fSynchronizeButton->Message()->what = kMsgStopSynchronization;
break;
}
case kMsgSynchronizationResult:
{
_DoneSynchronizing();
status_t status;
if (message->FindInt32("status", (int32 *)&status) == B_OK) {
if (status == B_OK)
return;
const char* errorString;
message->FindString("error string", &errorString);
char buffer[256];
int32 errorCode;
if (message->FindInt32("error code", &errorCode) == B_OK) {
snprintf(buffer, sizeof(buffer),
B_TRANSLATE("The following error occured "
"while synchronizing:\r\n%s: %s"),
errorString, strerror(errorCode));
} else {
snprintf(buffer, sizeof(buffer),
B_TRANSLATE("The following error occured "
"while synchronizing:\r\n%s"),
errorString);
}
BAlert* alert = new BAlert(B_TRANSLATE("Time"), buffer,
B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
break;
}
case kMsgRevert:
fSettings.Revert();
fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers());
fSynchronizeAtBootCheckBox->SetValue(
fSettings.GetSynchronizeAtBoot());
_UpdateServerList();
break;
default:
BGroupView::MessageReceived(message);
break;
}
}
void
NetworkTimeView::AttachedToWindow()
{
fServerTextControl->SetTarget(this);
fServerListView->SetTarget(this);
fAddButton->SetTarget(this);
fAddButton->SetEnabled(false);
fRemoveButton->SetTarget(this);
fResetButton->SetTarget(this);
fTryAllServersCheckBox->SetTarget(this);
fSynchronizeAtBootCheckBox->SetTarget(this);
fSynchronizeButton->SetTarget(this);
}
bool
NetworkTimeView::CheckCanRevert()
{
return fSettings.SettingsChanged();
}
void
NetworkTimeView::_InitView()
{
fServerTextControl = new BTextControl(NULL, NULL,
new BMessage(kMsgAddServer));
fServerTextControl->SetModificationMessage(new BMessage(kMsgServerEdited));
const float kButtonWidth = fServerTextControl->Frame().Height();
fAddButton = new BButton("add", "+", new BMessage(kMsgAddServer));
fAddButton->SetToolTip(B_TRANSLATE("Add"));
fAddButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth));
fRemoveButton = new BButton("remove", "−", new BMessage(kMsgRemoveServer));
fRemoveButton->SetToolTip(B_TRANSLATE("Remove"));
fRemoveButton->SetExplicitSize(BSize(kButtonWidth, kButtonWidth));
fServerListView = new BListView("serverList");
fServerListView->SetExplicitMinSize(BSize(B_SIZE_UNSET, kButtonWidth * 4));
fServerListView->SetSelectionMessage(new BMessage(kMsgSetDefaultServer));
BScrollView* scrollView = new BScrollView("serverScrollView",
fServerListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
_UpdateServerList();
fTryAllServersCheckBox = new BCheckBox("tryAllServers",
B_TRANSLATE("Try all servers"), new BMessage(kMsgTryAllServers));
fTryAllServersCheckBox->SetValue(fSettings.GetTryAllServers());
fSynchronizeAtBootCheckBox = new BCheckBox("autoUpdate",
B_TRANSLATE("Synchronize at boot"),
new BMessage(kMsgSynchronizeAtBoot));
fSynchronizeAtBootCheckBox->SetValue(fSettings.GetSynchronizeAtBoot());
fResetButton = new BButton("reset",
B_TRANSLATE("Reset to default server list"),
new BMessage(kMsgResetServerList));
fSynchronizeButton = new BButton("update", B_TRANSLATE("Synchronize"),
new BMessage(kMsgSynchronize));
BLayoutBuilder::Group<>(this, B_VERTICAL)
.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
.Add(fServerTextControl)
.Add(fAddButton)
.End()
.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
.Add(scrollView)
.AddGroup(B_VERTICAL, B_USE_SMALL_SPACING)
.Add(fRemoveButton)
.AddGlue()
.End()
.End()
.End()
.AddGroup(B_HORIZONTAL)
.AddGroup(B_VERTICAL, 0)
.Add(fTryAllServersCheckBox)
.Add(fSynchronizeAtBootCheckBox)
.End()
.End()
.AddGroup(B_HORIZONTAL)
.AddGlue()
.Add(fResetButton)
.Add(fSynchronizeButton)
.End()
.SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING,
B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING);
}
void
NetworkTimeView::_UpdateServerList()
{
BListItem* item;
while ((item = fServerListView->RemoveItem((int32)0)) != NULL)
delete item;
const char* server;
int32 index = 0;
while ((server = fSettings.GetServer(index++)) != NULL)
fServerListView->AddItem(new BStringItem(server));
fServerListView->Select(fSettings.GetDefaultServer());
fServerListView->ScrollToSelection();
fRemoveButton->SetEnabled(fServerListView->CountItems() > 0);
}
void
NetworkTimeView::_DoneSynchronizing()
{
fUpdateThread = -1;
fSynchronizeButton->SetLabel(B_TRANSLATE("Synchronize again"));
fSynchronizeButton->Message()->what = kMsgSynchronize;
}
bool
NetworkTimeView::_IsValidServerName(const char* serverName)
{
if (serverName == NULL || *serverName == '\0')
return false;
for (int32 i = 0; serverName[i] != '\0'; i++) {
char c = serverName[i];
// Simple URL validation, no scheme should be present
if (!(isalnum(c) || c == '.' || c == '-' || c == '_'))
return false;
}
return true;
}
// #pragma mark - update functions
int32
update_thread(void* params)
{
BList* list = (BList*)params;
BMessenger* messenger = (BMessenger*)list->ItemAt(1);
const char* errorString = NULL;
int32 errorCode = 0;
status_t status = update_time(*(Settings*)list->ItemAt(0),
&errorString, &errorCode);
BMessage result(kMsgSynchronizationResult);
result.AddInt32("status", status);
result.AddString("error string", errorString);
if (errorCode != 0)
result.AddInt32("error code", errorCode);
messenger->SendMessage(&result);
delete messenger;
return B_OK;
}
status_t
update_time(const Settings& settings, BMessenger* messenger,
thread_id* thread)
{
BList* params = new BList(2);
params->AddItem((void*)&settings);
params->AddItem((void*)messenger);
*thread = spawn_thread(update_thread, "ntpUpdate", 64, params);
return resume_thread(*thread);
}
status_t
update_time(const Settings& settings, const char** errorString,
int32* errorCode)
{
int32 defaultServer = settings.GetDefaultServer();
status_t status = B_ENTRY_NOT_FOUND;
const char* server = settings.GetServer(defaultServer);
if (server != NULL)
status = ntp_update_time(server, errorString, errorCode);
if (status != B_OK && settings.GetTryAllServers()) {
for (int32 index = 0; ; index++) {
if (index == defaultServer)
index++;
server = settings.GetServer(index);
if (server == NULL)
break;
status = ntp_update_time(server, errorString, errorCode);
if (status == B_OK)
break;
}
}
return status;
}
↑ V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.