/***************************************************************
 * Purpose:   Code for Application Frame
 *
 *  Copyright (C) 2010 Sourcefire, Inc.
 *
 *  Authors: Török Edwin
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 **************************************************************/

#include "wx_pch.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "../../../../libclamav/version.h"
#ifndef REPO_VERSION
#define __PLATFORM_H
#include "clamav-config.h"
#define REPO_VERSION VERSION
#endif

#include <WinSock.h>
#define OWN_WINSOCK

#include "SigUIMain.h"
#include "installdb.h"
#include <wx/clipbrd.h>
#include <wx/textdlg.h>
#include <wx/uri.h>
#include <wx/socket.h>
#include <wx/tokenzr.h>
#include <wx/txtstrm.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/dir.h>
#include <wx/hashset.h>

#if wxUSE_DRAG_AND_DROP
class DropFiles : public wxFileDropTarget
{
public:
    DropFiles(wxControlWithItems *owner)
	:m_owner(owner) {}

    virtual bool OnDropFiles(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y),
                             const wxArrayString& filenames)
    {
        unsigned n = filenames.GetCount();
        for (unsigned i=0;i<n;i++) {
            if (!wxFile::Exists(filenames[i])) {
		//TODO: show error
		return false;
	    }
	}
        for (unsigned i=0;i<n;i++) {
	    m_owner->Append(filenames[i]);
        }
	return true;
    }

private:
    wxControlWithItems *m_owner;
};
#endif

class HostnameValidator : public wxTextValidator
{
    protected:
	virtual wxString IsValid(const wxString &val) const {
	    if (val.length() == 1)
            return "";//characters
	    wxURI uri;
	    if (!uri.Create("http://" + val))
		return _("Invalid URI: %s");
	    if (uri.HasFragment() ||
		uri.HasPath() ||
		uri.HasPort() ||
		uri.HasQuery() ||
		!uri.HasServer() ||
		uri.HasUserInfo())
		return _("Invalid hostname: %s");
	    return "";
	}
    public:
	HostnameValidator(wxString *valPtr=NULL)
	    :wxTextValidator(wxFILTER_NONE, valPtr) {}
	virtual wxObject *Clone() const {
	    HostnameValidator *v = new HostnameValidator();
	    v->Copy(*this);
	    return v;
	}
};

SigUIFrame::~SigUIFrame()
{
    if (watcher)
        delete watcher;
    delete icon;
    delete editor;
}

void SigUIFrame::OnClose(wxCloseEvent& event)
{
    if (event.CanVeto()) {
	if (!Validate() || !TransferDataFromWindow()) {
	    wxLogWarning(_("Invalid data entered"));
	    event.Veto();
	    return;
	}
	if (!m_sig_candidates->IsEmpty()) {
	    int answer = wxMessageBox(_("You have added new virus signatures that have not been installed yet\n"
					"Are you sure you want to exit?"),
				      _("There are new signatures"),
				      wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION, this);
	    if (answer == wxNO) {
		event.Veto();
		return;
	    }
	}

	if (editor->Save(true)) {
	    int answer = wxMessageBox(_("There are unsaved changes that will be lost if you exit the application\n"
					"Save changes?"),
				      _("There are unsaved changes"),
				      wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION, this);
	    switch (answer) {
		case wxYES:
		    editor->Save();
		    break;
		case wxNO:
		    // exiting and loosing changes
		    break;
		default:
		    event.Veto();
		    return;
	    }
	}
    }

//    icon->RemoveIcon();
    Destroy();
}

void SigUIFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
{
    Destroy();
}

void SigUIFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
}

void SigUIFrame::m_proxyOnCheckBox( wxCommandEvent& event )
{
    bool enable = m_proxy->IsChecked();
    m_proxy_server->Enable(enable);
    m_proxy_port->Enable(enable);
    m_proxyauth->Enable(enable);
    m_proxyauthOnCheckBox(event);
}

void SigUIFrame::m_proxyauthOnCheckBox( wxCommandEvent& WXUNUSED(event) )
{
    bool enable = m_proxyauth->IsEnabled() && m_proxyauth->IsChecked();
    m_proxy_user->Enable(enable);
    m_proxy_password->Enable(enable);
}

class URLValidator : public wxTextValidator
{
    public:
	virtual wxString IsValid(const wxString &val) const {
	    if (val.IsEmpty())
		return _("Empty URLs are not valid: %s");
	    if (val.length() == 1)
		return "";//characters
	    //bb #2343
	    if (!*val.mb_str())
            return _("URL can't contain non-ASCII characters, please URLencode it: %s");

	    if (val.StartsWith("\\\\")) {
		if (!wxFileName::FileExists(val))
		    return _("UNC path doesn't exist: %s");
		return "";
	    }

	    wxURI uri;
	    if (!uri.Create(val))
		return _("Invalid URI: %s");
	    if (uri.HasUserInfo())
		return _("User not supported in URL: %s");
	    if (uri.HasQuery())
		return _("Query parameters not supported in URL: %s");
	    if (uri.HasFragment())
		return _("Fragment not supported in URL: %s");

	    if (uri.GetScheme() == "file") {
		if (!wxFileName::FileExists(uri.GetPath()))
		    return _("File doesn't exist: %s");
		return "";
	    }
	    if (uri.GetScheme() == "http" && uri.IsReference())
		return _("URL not absolute: %s");
	    if (uri.GetScheme() != "http" && uri.GetScheme() != "file")
		return _("Only HTTP URLs accepted: %s");
	    if (!uri.HasServer())
		return _("URL must specify a server: %s");
	    if (!uri.HasPath())
		return _("URL must specify a path: %s");
	    if (!SigUICopy::validate_dbname(uri.GetPath(), false))
		return _("The extension is not a valid virus signature database extension: %s");
	    return "";
	}
    public:
	URLValidator(wxString *valPtr=NULL)
	    :wxTextValidator(wxFILTER_NONE, valPtr) {}
	virtual wxObject *Clone() const {
	    URLValidator *v = new URLValidator();
	    v->Copy(*this);
	    return v;
	}
};

class URLEntryDialog : public wxTextEntryDialog
{
    public:
    URLEntryDialog(wxWindow* parent, const wxString& message, const wxString& caption = "Please enter text", const wxString& defaultValue = "", long style = wxOK | wxCANCEL | wxCENTRE, const wxPoint& pos = wxDefaultPosition)
	: wxTextEntryDialog(parent, message, caption, defaultValue, style, pos) {
	    URLValidator validator(&m_value);
	    SetTextValidator(validator);
	}
};

#if wxUSE_DRAG_AND_DROP
class DropURLs : public wxDropTarget
{
public:
    DropURLs(wxControlWithItems *owner)
	: m_owner(owner)
    {
	SetDataObject(new wxURLDataObject);
    }

    virtual wxDragResult OnDragOver(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y),
				    wxDragResult WXUNUSED(def))
    {
	return wxDragLink;
    }

    virtual wxDragResult OnData(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), wxDragResult def)
    {
        if (!GetData())
	    return wxDragNone;

	wxString url = ((wxURLDataObject*)GetDataObject())->GetURL();
	url.Trim();
	url.Trim(false);

	wxArrayString good;

	wxStringTokenizer tokenizer(url);//tokenize lines
	while (tokenizer.HasMoreTokens()) {
	    wxString token = tokenizer.GetNextToken();
	    token.Trim();

	    URLValidator urlv;
	    wxString err = urlv.IsValid(token);
	    if (!err.IsEmpty()) {
		wxString buf;
		buf.Printf(err, token.c_str());
		wxMessageBox(buf, _("Validation conflict"),
			     wxOK | wxICON_EXCLAMATION, m_owner);
		return wxDragError;
	    }
	    good.Add(token);
	}
	for (unsigned i=0;i<good.GetCount();i++)
	    m_owner->Append(good[i]);

        return def;
    }
private:
    wxControlWithItems *m_owner;
};
#endif

WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, StringSet);

static wxString GetBasename(wxString path)
{
    return wxFileName(path).GetFullName().MakeLower();
}

void SigUIFrame::GetFreshclamDBnames(StringSet *set)
{
    set->insert("main.cvd");
    set->insert("daily.cvd");
    set->insert("bytecode.cvd");
    for (unsigned i=0;i<m_urls->GetCount();i++) {
	set->insert( GetBasename(m_urls->GetString(i)) );
    }
}

void SigUIFrame::m_custom_addOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    URLEntryDialog dlg(this, _("Custom virus signatures source HTTP or file URL:"), _("Input http:// file:// URL or UNC path:"));
    if (dlg.ShowModal() == wxID_OK) {
	wxString str = dlg.GetValue();
	if (str.StartsWith("\\\\")) {
	    wxLogWarning(_("You specified an UNC path, make sure the SYSTEM account can access it!\n"
			   "(SYSTEM account usually can't access network shares)"));
	    str = "file://" + str;//freshclam remove file:// so it should be fine
	}

	// look for duplicate basename (since it will they will just overwrite
	// eachother in the DBdir)
	StringSet db_set;
	GetFreshclamDBnames(&db_set);
	wxString basename = GetBasename(str);

	if (db_set.count(basename)) {
	    wxLogWarning(_("Adding this database (%s) will overwrite existing database (%s)!\n"
			   "You should remove one of them from the custom signatures list"),
			 str, basename);
	}

	m_urls->Append(str);
	m_custom_remove->Enable();
    }
}

void SigUIFrame::m_custom_removeOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    int n = m_urls->GetSelection();
    if (n == wxNOT_FOUND)
	return;
    m_urls->Delete(n);
    if (m_urls->IsEmpty())
	m_custom_remove->Disable();
}

static wxString GetExecPath()
{
    wxFileName exec(wxStandardPaths::Get().GetExecutablePath());
    return exec.GetPathWithSep();
}

static wxFileSystemWatcher *watcher = 0;
static bool watcher_deleted = false;
void SigUIApp::OnEventLoopEnter(wxEventLoopBase *)
{
    if (!watcher && !watcher_deleted) {
        watcher = new wxFileSystemWatcher();
        watcher->SetOwner(GetTopWindow());
        watcher->Add(GetExecPath(), wxFSW_EVENT_CREATE | wxFSW_EVENT_MODIFY |
            wxFSW_EVENT_WARNING | wxFSW_EVENT_ERROR);
    }
}

void SigUIApp::OnEventLoopExit(wxEventLoopBase *WXUNUSED(loop))
{
    if (watcher) {
        delete watcher;
        watcher_deleted = true;
        watcher = 0;
    }
}

void SigUIFrame::m_save_settingsOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    if (!Validate() || !TransferDataFromWindow()) {
        wxLogWarning(_("Data entered is invalid"));
        return;
    }
    editor->Save();
    wxLogMessage(_("Settings saved"));
}

static wxString GetConfigFile()
{
    return GetExecPath() + "freshclam.conf";
}
SigUIFrame::SigUIFrame(wxFrame *frame)
    : GUIFrame(frame), val_bytecode(true), watcher(0)
{
    #ifdef _WIN32
    SetIcon(wxIcon(wxT("aaaa")));
    #endif

    icon = new wxTaskBarIcon();
    icon->Connect(wxEVT_TASKBAR_BALLOON_TIMEOUT, wxTaskBarIconEventHandler(SigUIFrame::OnBalloon), NULL, this);
    icon->Connect(wxEVT_TASKBAR_BALLOON_CLICK, wxTaskBarIconEventHandler(SigUIFrame::OnBalloon), NULL, this);

    this->Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(SigUIFrame::OnChange));

    this->Connect(wxEVT_END_PROCESS, wxProcessEventHandler(SigUIFrame::OnTerminateInstall));

    this->SetStatusBar(statusBar);
    statusBar->SetStatusText(REPO_VERSION, 1);

//    m_sig_files->SetDropTarget(new DropFiles(m_sig_files));
//    m_urls->SetDropTarget(new DropURLs(m_urls));


    editor = new ConfigEditor(GetConfigFile());
    editor->RegisterText("HTTPProxyServer", &val_proxy_server, m_proxy_server, "hostname or IP address");
    editor->RegisterInt("HTTPProxyPort", &val_proxy_port, m_proxy_port);
    editor->RegisterText("HTTPProxyUsername", &val_proxy_username, m_proxy_user);
    editor->RegisterText("HTTPProxyPassword", &val_proxy_password, m_proxy_password);
    editor->RegisterText("DatabaseMirror", &val_mirror, m_mirror, "db.COUNTRYCODE.clamav.net");
    editor->RegisterStatic("DatabaseMirror", "db.local.win.clamav.net");
    editor->RegisterBool("Bytecode", &val_bytecode, m_bytecode);
    editor->RegisterList("DatabaseCustomURL", m_urls);

    HostnameValidator mirrorValidator(&val_mirror);
    m_mirror->SetValidator(mirrorValidator);

    HostnameValidator proxyValidator(&val_proxy_server);
    m_proxy_server->SetValidator(proxyValidator);

    editor->Load();
    if (!val_proxy_port)
        val_proxy_port = 8080;//default

    TransferDataToWindow();

    if (!val_proxy_server.empty()) {
	m_proxy->SetValue(true);
	if (!val_proxy_username.empty() && !val_proxy_password.empty())
	    m_proxyauth->SetValue(true);
    }

    // update enabled status
    wxCommandEvent event;
    m_proxyOnCheckBox(event);

    if (!m_urls->IsEmpty())
	m_custom_remove->Enable();

	m_proxy->SetFocus();

    // keyboard shortcuts
    wxAcceleratorEntry entries[1];
    entries[0].Set(wxACCEL_CTRL, (int)'S', wxID_SAVE);

    wxAcceleratorTable accel(sizeof(entries)/sizeof(entries[0]), entries);
    this->SetAcceleratorTable(accel);

    //prevent window from being resizing below minimum
    this->GetSizer()->SetSizeHints(this);
    show_db(true);
}

void SigUIFrame::OnBalloon(wxTaskBarIconEvent& WXUNUSED(event))
{
    if (icon->IsIconInstalled())
	icon->RemoveIcon();
}

void SigUIFrame::OnChange(wxFileSystemWatcherEvent &event)
{
    if (event.IsError()) {
	wxLogVerbose("fswatcher error: %s", event.GetErrorDescription());
	return;
    }
    wxLogVerbose("event on %s", event.GetPath().GetFullPath());
    switch (event.GetChangeType()) {
	default:
	    break;
	case wxFSW_EVENT_CREATE:
	case wxFSW_EVENT_MODIFY:
	    wxFileName filename = event.GetPath();
	    if (filename.GetName() != "lastupd")
		return;
	    show_db(false);
	    break;
    }
}

void SigUIFrame::show_db(bool first)
{
    wxLogNull logNo;
    char msg[512];
    wxFileName filename(GetExecPath() + "lastupd");
    if (!filename.IsFileReadable())
	return;
    wxFile file(filename.GetFullPath());
    if (!file.IsOpened())
	return;
    memset(&msg, 0, sizeof(msg));
    if (file.Read(msg, sizeof(msg) - 1) <= 0)
	return;

    wxString line = wxString(msg).BeforeFirst('\n');
    wxString text = statusBar->GetStatusText(0);
    statusBar->SetStatusText(line, 0);
    if (first || lastmsg == msg)
	return;
    lastmsg = msg;
    //only show when changed, and not the first time
    if (icon->IsIconInstalled())
	icon->RemoveIcon();//remove old balloon
    icon->SetIcon(GetIcon());
    line = wxString(msg).AfterFirst('\n');
#ifdef _WIN32
    icon->ShowBalloon(_("ClamAV database reloaded"),
		      line, wxICON_INFORMATION);
#endif
    wxFileName filename0(GetExecPath() + "forcerld");
    wxLogVerbose("Reload delta: %s", filename.GetModificationTime().Subtract( filename0.GetModificationTime() ).Format());
}

void SigUIFrame::tabsOnNotebookPageChanged( wxNotebookEvent& event )
{
    event.Skip();
}

void SigUIFrame::GUIFrameOnIdle(wxIdleEvent& WXUNUSED(event))
{
    wxArrayString dbfiles;
    wxDir dir(GetExecPath());
    if (!dir.IsOpened())
	return;
    wxString filename;
    bool cont = dir.GetFirst(&filename);
    while (cont) {
	if (SigUICopy::validate_dbname(filename, true))
	    dbfiles.Add(filename);
	cont = dir.GetNext(&filename);
    }
    dbfiles.Sort();

    wxArrayString old_dbfiles = m_installed_sigs->GetStrings();
    if (old_dbfiles != dbfiles) {
	m_installed_sigs->Clear();
	m_installed_sigs->Append(dbfiles);
    }
}

class MyProcess : public wxProcess
{
    public:
	MyProcess(MyProcessOutput *parent) :
	    m_parent(parent) { }
	virtual void OnTerminate(int pid, int status)
	{
	    wxProcessEvent event(0,pid,status);
	    m_parent->OnTerminate(event);
	}
    private:
	MyProcessOutput *m_parent;
};

void SigUIFrame::reload()
{
    wxFileName filename(GetExecPath() + "forcerld");
    if (!filename.FileExists()) {
	wxFile file;
	if (!file.Create(filename.GetFullPath(), true)) {
	    wxLogMessage(_("Cannot signal reload"));
	    return;
	}
    } else {
	filename.Touch();
    }

    wxLogMessage(_("Database reload queued"));
}

void SigUIFrame::m_run_freshclamOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    MyProcessOutput *output = new MyProcessOutput(this);
    wxProcess *process = new MyProcess(output);
    process->Redirect();

    wxString cmd;
    cmd << "\"" << GetExecPath() << "freshclam.exe\" -v --config-file=\""
	<< GetConfigFile() << "\" --datadir=\"" << GetExecPath() << "\"";
    //wxMessageBox(cmd);
    long pid = wxExecute(cmd, wxEXEC_ASYNC, process);
    if (!pid) {
	wxLogError(_("Failed to launch freshclam"));
	delete process;
	return;
    }
    process->SetPid(pid);
    process->CloseOutput();

    wxInputStream *in = process->GetInputStream();
    if (!in)
    {
        wxLogError(_("Failed to connect to child process output"));
        return;
    }

    output->SetProcess(process);
    output->ShowModal();
    reload();
}

MyProcessOutput::MyProcessOutput(wxWindow *parent)
    : ProcessOutput(parent), m_process(0), m_wakeup(this)
{
    this->Connect(wxEVT_END_PROCESS, wxProcessEventHandler(MyProcessOutput::OnTerminate));
    this->Connect(wxEVT_TIMER, wxTimerEventHandler(MyProcessOutput::OnTimer),NULL, this);
    this->GetSizer()->SetSizeHints(this);
    this->SetDoubleBuffered(true);
}

void MyProcessOutput::SetProcess(wxProcess *process)
{
    m_process = process;
}

void MyProcessOutput::ProcessOutputOnInitDialog( wxInitDialogEvent& WXUNUSED(event) )
{
    m_wakeup.Start(100);
}

void MyProcessOutput::ProcessOutputOnClose(wxCloseEvent &event)
{
    if (m_process) {
        if (event.CanVeto()) {
            event.Veto();
            return;
        }
        m_process->Detach();
    }
    EndModal(wxOK);
}

void MyProcessOutput::OnTimer(wxTimerEvent& WXUNUSED(event))
{
    wxWakeUpIdle();
}

bool MyProcessOutput::processInput()
{
    if (!m_process)
        return false;
    bool hasInput = false;
    m_logoutput->Freeze();
    while (m_process->IsInputAvailable()) {
        wxInputStream *in = m_process->GetInputStream();
        wxString msg;
	int c;
	static bool clear = false;

	do {
	    c = in->GetC();
	    if (in->Eof())
		break;
	    if (c >= 128)
		c = '?';
	    msg << (char)c;
	} while (c != '\r' && c != '\n');

	msg.Trim();
	msg.Trim(false);
	if (!msg.empty()) {
	    bool scroll = false;
	    if (clear && m_logoutput->GetCount() > 0)
		m_logoutput->Delete(m_logoutput->GetCount()-1);
	    else
		scroll = true;
	    m_logoutput->Append(msg);
	    m_logoutput->ScrollLines(1);
	}
	clear = c == '\r';
        hasInput = true;
    }
    while (m_process->IsErrorAvailable()) {
        wxTextInputStream tis(*m_process->GetErrorStream());
        wxString msg;
        msg << tis.ReadLine();
	msg.Trim();
	if (!msg.empty()) {
	    m_logoutput->Append(msg);
	    m_logoutput->ScrollLines(1);
	}
        hasInput = true;
    }
    m_logoutput->Thaw();
    return hasInput;
}

void MyProcessOutput::ProcessOutputOnIdle( wxIdleEvent& event )
{
    if (processInput())
	event.RequestMore();
}

void MyProcessOutput::OnTerminate(wxProcessEvent &event)
{
    m_wakeup.Stop();
    // show all output
    while (processInput()) {}

    int exit = event.GetExitCode();
    delete m_process;
    m_process = 0;

    m_cancel_process->SetLabel(_("&Close window"));
    wxString msg;
    msg << _("Freshclam exited with code: ") << exit;
    m_logoutput->Append(msg);
}

void MyProcessOutput::m_cancel_processOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    if (m_process) {
	long pid = m_process->GetPid();
	wxLogVerbose("terminate pid %ld", pid);
	if (!wxProcess::Exists(pid)) {
	    wxLogVerbose("process doesn't exist anymore");
	    return;
	}
	int answer = wxMessageBox(_("Are you sure you want to forcefully terminate freshclam?"),
                           _("Force terminate freshclam?"),
				  wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION, this);
	if (answer != wxYES)
	    return;
	wxLogVerbose("kill pid %ld", pid);
	wxKillError rc =  wxProcess::Kill(pid, wxSIGKILL);
	if (rc != wxKILL_OK) {
	    wxLogVerbose("kill pid %ld failed: %d", pid, rc);
	    wxLogWarning(_("Failed to terminate process"));
	    return;
	}
	wxLogVerbose("killed pid %ld", pid);
/*	wxProcessEvent event(0,pid,255);
	OnTerminate(event);*/
	return;
    }
    // this is really the close button now
    EndModal(wxOK);
}

void SigUIFrame::m_local_addOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    //TODO: keep in sync with DBEXT
    wxString wildcard = wxString(_("ClamAV database files")) + " (*.cbc, *.cdb, *.cfg, *.cld, *.cvd, *.db, *.fp, *.ftm, *.gdb, *.hdb, *.hdu, *.idb, *.ldb, *.ldu, *.mdb, *.mdu, *.ndb, *.ndu, *.pdb, *.rmd, *.sdb, *.wdb, *.zmd)|*.cbc;*.cdb;*.cfg;*.cld;*.cvd;*.db;*.fp;*.ftm;*.gdb;*.hdb;*.hdu;*.idb;*.ldb;*.ldu;*.mdb;*.mdu;*.ndb;*.ndu;*.pdb;*.rmd;*.sdb;*.wdb;*.zmd";
    wxFileDialog dlg(this, _("Choose a virus signature file"),
		     wxEmptyString, wxEmptyString,
		     wildcard,
		     wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR);
    dlg.CentreOnParent();
    if (dlg.ShowModal() == wxID_OK) {
	wxArrayString paths;
	dlg.GetPaths(paths);
	StringSet db_set;
	GetFreshclamDBnames(&db_set);

	for (unsigned i=0;i<paths.GetCount();i++) {
	    wxString path = paths[i];
	    if (m_sig_candidates->FindString(path, false) != wxNOT_FOUND) {
		wxLogWarning(_("File already added: %s"), path);
		continue;
	    }
	    if (db_set.count(GetBasename(path))) {
		wxLogWarning(_("File is managed by freshclam. On next update it will be overwritten: %s"), path);
	    }

	    m_sig_candidates->Append(path);
	}
	m_local_remove->Enable(!m_sig_candidates->IsEmpty());
	m_install->Enable(!m_sig_candidates->IsEmpty());
    }
}

void SigUIFrame::m_local_removeOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    wxArrayInt selections;
    int n = m_sig_candidates->GetSelections(selections);
    while ( n > 0 )
    {
        m_sig_candidates->Delete(selections[--n]);
    }
    m_local_remove->Enable(!m_sig_candidates->IsEmpty());
    m_install->Enable(!m_sig_candidates->IsEmpty());
}

void SigUIFrame::OnTerminateInstall(wxProcessEvent &event)
{
    wxEndBusyCursor();
    wxWakeUpIdle();
    if (event.GetExitCode() == 0) {
	m_sig_candidates->Clear();
	wxLogMessage(_("Successfully installed new virus signatures\n"));
	reload();
    } else {
	bool had_errors = false;
	wxInputStream *err = m_siginst_process->GetErrorStream();
	wxTextInputStream tis(*err);

	while (!err->Eof()) {
	    wxString line = tis.ReadLine();
	    line.Trim();
	    if (!line.IsEmpty()) {
		wxLogWarning("%s", line);
		had_errors = true;
	    }
	}
	if (had_errors) {
	    wxLogError(_("Errors encountered during virus signature install"));
	}
    }
    delete m_siginst_process;

    m_panel_sigman->Enable();
}

void SigUIFrame::m_installOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    wxWakeUpIdle();
    wxBeginBusyCursor();
    m_panel_sigman->Disable();

    wxFileName exec(wxStandardPaths::Get().GetExecutablePath());
    m_siginst_process = new wxProcess(this);
    m_siginst_process->Redirect();

    long pid = wxExecute("\"" + exec.GetFullPath() + "\" -i", wxEXEC_ASYNC | wxEXEC_NOHIDE, m_siginst_process);
    if (!pid) {
	wxLogError(_("Failed to reexecute self for installing the virus signatures!"));
	return;
    }
    m_siginst_process->SetPid(pid);

    wxOutputStream *out = m_siginst_process->GetOutputStream();
    for (unsigned i=0;i<m_sig_candidates->GetCount();i++) {
	wxString str = m_sig_candidates->GetString(i) + "\n";
	const char *s = str.mb_str();
	if (*s) {
	    out->Write(s, strlen(s));
	} else {
	    // see bb #2343
	    wxLogError(_("Filenames with non-ASCII characters not yet supported: %s"), str);
	    return;
	}
    }
    m_siginst_process->CloseOutput();
    wxWakeUpIdle();
}

void SigUIFrame::m_deleteOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    wxArrayInt selections;
    int n = m_installed_sigs->GetSelections(selections);
    while ( n > 0 )
    {
	wxString file = m_installed_sigs->GetString(selections[--n]);
	if (file.CmpNoCase("daily.cvd") == 0 ||
	    file.CmpNoCase("daily.cld") == 0) {
	    wxLogError(_("daily.cvd and daily.cld cannot be removed!"));
	    continue;
	}

	wxString msg;
	msg.Printf(_("Are you sure you want to delete  %s?"), file);
	int answer = wxMessageBox(msg, _("Delete virus signature database"),
				  wxYES_NO | wxCANCEL | wxNO_DEFAULT | wxICON_QUESTION, this);
	if (answer == wxCANCEL)
	    break;
	if (answer != wxYES)
	    continue;

	if (file.AfterLast('.').CmpNoCase("cvd") == 0 ||
	    file.AfterLast('.').CmpNoCase("cld") == 0) {
	    msg.Printf(_("This is an important database file, managed by freshclam.\nAre you sure you want to delete %s?"),
		       file);
	    answer = wxMessageBox(msg, _("Delete important virus signature database"),
				  wxYES_NO | wxCANCEL | wxNO_DEFAULT | wxICON_QUESTION, this);
	    if (answer == wxCANCEL)
		break;
	    if (answer != wxYES)
		continue;
	}

	wxFileName filepath(GetExecPath(), file);
	if (!wxRemoveFile(filepath.GetFullPath())) {
	    wxLogError(_("Can't remove file %s"), filepath.GetFullPath());
	} else
	    reload();
    }

    wxWakeUpIdle();
}

void SigUIFrame::m_bytecodeOnCheckBox( wxCommandEvent& WXUNUSED(event) )
{
    bool enable = m_bytecode->IsChecked();
    if (!enable) {
	int answer = wxMessageBox(_("It is NOT recommended to disable bytecode.\n"
				    "Are you sure you want to disable it?"),
				  _("Disabling important signature database"),
				  wxYES_NO | wxCANCEL | wxNO_DEFAULT | wxICON_QUESTION, this);
	if (answer != wxYES)
	    m_bytecode->SetValue(true);
    }
}