root/src/add-ons/network_settings/dnsclient/DNSSettingsView.cpp
/*
 * Copyright 2014-2015 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Adrien Destugues, pulkomandy@pulkomandy.tk
 */


#include "DNSSettingsView.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <resolv.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>

#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <File.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <ListView.h>
#include <Path.h>
#include <SeparatorView.h>
#include <ScrollView.h>
#include <StringView.h>

#include "IPAddressControl.h"


static const int32 kMsgAddServer = 'adds';
static const int32 kMsgDeleteServer = 'dels';
static const int32 kMsgSelectServer = 'sels';
static const int32 kMsgMoveUp = 'mvup';
static const int32 kMsgMoveDown = 'mvdn';
static const int32 kMsgApply = 'aply';


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "DNSSettingsView"


DNSSettingsView::DNSSettingsView(BNetworkSettingsItem* item)
        :
        BView("dns", 0),
        fItem(item)
{
        BStringView* titleView = new BStringView("title",
                B_TRANSLATE("DNS settings"));
        titleView->SetFont(be_bold_font);
        titleView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

        fServerListView = new BListView("nameservers");
        fServerListView->SetSelectionMessage(new BMessage(kMsgSelectServer));
        const char* serverLabel = B_TRANSLATE("Server:");
        fTextControl = new IPAddressControl(AF_UNSPEC, serverLabel, "server");
        fTextControl->SetExplicitMinSize(BSize(fTextControl->StringWidth("5") * 16
                + fTextControl->StringWidth(serverLabel), B_SIZE_UNSET));

        fAddButton = new BButton(B_TRANSLATE("Add"), new BMessage(kMsgAddServer));
        fAddButton->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
        fUpButton = new BButton(B_TRANSLATE("Move up"), new BMessage(kMsgMoveUp));
        fUpButton->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
        fUpButton->SetEnabled(false);
        fDownButton = new BButton(B_TRANSLATE("Move down"),
                new BMessage(kMsgMoveDown));
        fDownButton->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
        fDownButton->SetEnabled(false);
        fRemoveButton = new BButton(B_TRANSLATE("Remove"),
                new BMessage(kMsgDeleteServer));
        fRemoveButton->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
        fRemoveButton->SetEnabled(false);
        fDomain = new BTextControl(B_TRANSLATE("Domain:"), "", NULL);
        fApplyButton = new BButton(B_TRANSLATE("Apply"), new BMessage(kMsgApply));

        float spacing = be_control_look->DefaultItemSpacing();

        BGridView* serviceGridView = new BGridView();
        BLayoutBuilder::Grid<>(serviceGridView)
                .Add(fTextControl, 0, 0)
                .Add(fAddButton, 1, 0)
                .Add(new BScrollView("scroll", fServerListView, 0, false, true),
                        0, 1, 1, 3)
                .Add(fUpButton, 1, 1)
                .Add(fDownButton, 1, 2)
                .Add(fRemoveButton, 1, 3)
                .SetColumnWeight(0, 10.f);

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .Add(titleView)
                .Add(serviceGridView)
                .Add(new BSeparatorView(B_HORIZONTAL))
                .Add(fDomain)
                .AddStrut(spacing)
                .AddGroup(B_HORIZONTAL)
                        .AddGlue()
                        .Add(fApplyButton);

        _LoadDNSConfiguration();
}


DNSSettingsView::~DNSSettingsView()
{
}


status_t
DNSSettingsView::Revert()
{
        int i;
        for (i = 0; i < fRevertList.CountStrings(); i++) {
                BStringItem* item = static_cast<BStringItem*>(
                        fServerListView->ItemAt(i));
                if (item == NULL) {
                        item = new BStringItem("");
                        fServerListView->AddItem(item);
                }

                item->SetText(fRevertList.StringAt(i));
        }

        // Now remove any extra item
        for (; i < fServerListView->CountItems(); i++)
                delete fServerListView->RemoveItem(i);

        return B_OK;
}


bool
DNSSettingsView::IsRevertable() const
{
        // TODO
        return false;
}


void
DNSSettingsView::AttachedToWindow()
{
        fAddButton->SetTarget(this);
        fRemoveButton->SetTarget(this);
        fUpButton->SetTarget(this);
        fDownButton->SetTarget(this);

        fServerListView->SetTarget(this);

        fTextControl->SetTarget(this);

        fApplyButton->SetTarget(this);
}


void
DNSSettingsView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case kMsgAddServer:
                {
                        const char* address = fTextControl->Text();
                        fServerListView->AddItem(new BStringItem(address));
                        break;
                }
                case kMsgDeleteServer:
                        delete fServerListView->RemoveItem(
                                fServerListView->CurrentSelection());
                        break;

                case kMsgMoveUp:
                {
                        int index = fServerListView->CurrentSelection();
                        if (index > 0)
                                fServerListView->SwapItems(index, index - 1);
                        break;
                }
                case kMsgMoveDown:
                {
                        int index = fServerListView->CurrentSelection();
                        if (index < fServerListView->CountItems() - 1)
                                fServerListView->SwapItems(index, index + 1);
                        break;
                }
                case kMsgSelectServer:
                {
                        bool enabled = false;
                        if (fServerListView->CurrentSelection() > -1)
                                enabled = true;

                        fUpButton->SetEnabled(enabled);
                        fDownButton->SetEnabled(enabled);
                        fRemoveButton->SetEnabled(enabled);
                        break;
                }
                case kMsgApply:
                        if (_SaveDNSConfiguration() == B_OK)
                                fItem->NotifySettingsUpdated();
                        break;

                default:
                        BView::MessageReceived(message);
                        break;
        }
}


status_t
DNSSettingsView::_LoadDNSConfiguration()
{
        if (res_init() != 0)
                return B_ERROR;

        struct __res_state state;
        memset(&state, 0, sizeof(struct __res_state));

        if (res_ninit(&state) != 0)
                return B_ERROR;

        for (int i = 0; i < state.nscount; i++) {
                char* address = inet_ntoa(state.nsaddr_list[i].sin_addr);
                fServerListView->AddItem(new BStringItem(address));
                fRevertList.Add(address);
        }

        fDomain->SetText(state.dnsrch[0]);

        res_nclose(&state);
        return B_OK;
}


status_t
DNSSettingsView::_SaveDNSConfiguration()
{
        BPath path;
        status_t status;
        status = find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path);
        if (status != B_OK)
                return status;

        path.Append("network/resolv.conf");

        BFile file(path.Path(), B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
        if (file.InitCheck() != B_OK) {
                fprintf(stderr, "failed to open %s for writing: %s\n", path.Path(),
                        strerror(file.InitCheck()));
                return file.InitCheck();
        }

        BString content("# Static DNS entries\n# Generated by Network preferences\n");

        for (int i = 0; i < fServerListView->CountItems(); i++) {
                BString item = ((BStringItem*)fServerListView->ItemAt(i))->Text();
                if (item.Length() > 0)
                        content << "nameserver\t" << item.String() << "\n";
        }

        if (strlen(fDomain->Text()) > 0)
                content << "domain\t" << fDomain->Text() << "\n";

        content << "# Dynamic DNS entries\n# may be altered by DHCP\n";

        return file.Write(content.String(), content.Length());
}