#include "updatedlg.h"
#include <wx/xrc/xmlres.h>
#include <wx/msgdlg.h>
#include <wx/button.h>
#include <wx/gauge.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/combobox.h>
#include <wx/checkbox.h>
#include <wx/file.h>
#include <wx/menu.h>
#include <wx/log.h>
#include "devpakinstaller.h"
#include "crc32.h"
#include <configmanager.h>
#include <manager.h>
#include <messagemanager.h>
#include <globals.h>
int idNet = wxNewId();
int idPopupInstall = wxNewId();
int idPopupDownload = wxNewId();
int idPopupDownloadAndInstall = wxNewId();
int idPopupUninstall = wxNewId();
BEGIN_EVENT_TABLE(UpdateDlg, wxDialog)
EVT_UPDATE_UI(-1, UpdateDlg::OnUpdateUI)
EVT_TREE_SEL_CHANGED(XRCID("tvCategories"), UpdateDlg::OnTreeSelChanged)
EVT_LIST_ITEM_SELECTED(XRCID("lvFiles"), UpdateDlg::OnFileSelected)
EVT_LIST_ITEM_DESELECTED(XRCID("lvFiles"), UpdateDlg::OnFileDeSelected)
EVT_LIST_ITEM_RIGHT_CLICK(XRCID("lvFiles"), UpdateDlg::OnFileRightClick)
EVT_MENU(idPopupDownload, UpdateDlg::OnDownload)
EVT_MENU(idPopupDownloadAndInstall, UpdateDlg::OnDownloadAndInstall)
EVT_MENU(idPopupInstall, UpdateDlg::OnInstall)
EVT_MENU(idPopupUninstall, UpdateDlg::OnUninstall)
EVT_COMBOBOX(XRCID("cmbServer"), UpdateDlg::OnServerChange)
EVT_COMBOBOX(XRCID("cmbFilter"), UpdateDlg::OnFilterChange)
EVT_CHECKBOX(XRCID("chkCache"), UpdateDlg::OnServerChange)
EVT_CBNET_CONNECT(idNet, UpdateDlg::OnConnect)
EVT_CBNET_DISCONNECT(idNet, UpdateDlg::OnDisConnect)
EVT_CBNET_PROGRESS(idNet, UpdateDlg::OnProgress)
EVT_CBNET_ABORTED(idNet, UpdateDlg::OnAborted)
EVT_CBNET_START_DOWNLOAD(idNet, UpdateDlg::OnDownloadStarted)
EVT_CBNET_END_DOWNLOAD(idNet, UpdateDlg::OnDownloadEnded)
END_EVENT_TABLE()
UpdateDlg::UpdateDlg(wxWindow* parent)
: m_Recs(0),
m_RecsCount(0),
m_CurrFileSize(0),
m_LastBlockSize(0),
m_HasUpdated(false),
m_FirstTimeCheck(true),
m_Net(this, idNet, _T("http://devpaks.sourceforge.net/"))
{
//ctor
wxXmlResource::Get()->LoadDialog(this, parent, _T("MainFrame"));
CreateListColumns();
FillServers();
UpdateStatus(_("Ready"), 0);
}
UpdateDlg::~UpdateDlg()
{
//dtor
delete[] m_Recs;
m_RecsCount = 0;
}
void UpdateDlg::EndModal(int retCode)
{
if (!m_Net.IsConnected() || retCode != wxID_CANCEL)
{
wxDialog::EndModal(retCode);
return;
}
if (m_Net.IsConnected())
m_Net.Abort();
}
void UpdateDlg::CreateListColumns()
{
wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
lst->InsertColumn(0, _("Title"));
lst->InsertColumn(1, _("Version"));
lst->InsertColumn(2, _("Installed"));
lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);
lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space
lst->SetColumnWidth(1, 64);
lst->SetColumnWidth(2, 64);
lst->SetColumnWidth(3, 64);
}
void UpdateDlg::AddRecordToList(UpdateRec* rec)
{
if (!rec)
return;
wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
int idx = lst->GetItemCount();
lst->InsertItem(idx, rec->title);
lst->SetItem(idx, 1, rec->version);
lst->SetItem(idx, 2, rec->installed_version);
lst->SetItem(idx, 3, rec->size);
}
void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)
{
wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
wxListItem it;
it.m_itemId = index;
it.m_col = col;
it.m_mask = wxLIST_MASK_TEXT;
it.m_text = text;
lst->SetItem(it);
}
void UpdateDlg::UpdateStatus(const wxString& status, int curProgress, int maxProgress)
{
wxStaticText* lbl = XRCCTRL(*this, "lblStatus", wxStaticText);
if (lbl->GetLabel() != status)
lbl->SetLabel(status);
if (curProgress != -1)
XRCCTRL(*this, "gauProgress", wxGauge)->SetValue(curProgress);
if (maxProgress != -1)
XRCCTRL(*this, "gauProgress", wxGauge)->SetRange(maxProgress);
}
void UpdateDlg::EnableButtons(bool update, bool abort)
{
wxButton* btnCl = XRCCTRL(*this, "wxID_CANCEL", wxButton);
btnCl->Enable(abort);
// disable server list and cache checkbox while downloading
XRCCTRL(*this, "cmbServer", wxComboBox)->Enable(!m_Net.IsConnected());
XRCCTRL(*this, "chkCache", wxCheckBox)->Enable(!m_Net.IsConnected());
wxYield();
}
void UpdateDlg::FillGroups()
{
UpdateStatus(_("Parsing list of updates"), 0, m_RecsCount - 1);
// get a list of unique group names
wxArrayString groups;
for (int i = 0; i < m_RecsCount; ++i)
{
for (unsigned int x = 0; x < m_Recs[i].groups.GetCount(); ++x)
{
if (m_Recs[i].groups[x].IsEmpty())
continue;
if (groups.Index(m_Recs[i].groups[x]) == wxNOT_FOUND)
{
if (FilterRec(&m_Recs[i]))
groups.Add(m_Recs[i].groups[x]);
}
}
}
// create the groups tree
wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
tree->Freeze();
tree->DeleteAllItems();
wxTreeItemId root = tree->AddRoot(_("All categories"));
for (unsigned int i = 0; i < groups.GetCount(); ++i)
{
tree->AppendItem(root, groups[i]);
}
tree->SortChildren(root);
tree->Thaw();
tree->Expand(root);
tree->SelectItem(root); // this calls the event
UpdateStatus(_("Done parsing list of updates"), 0);
}
void UpdateDlg::FillFiles(const wxTreeItemId& id)
{
wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
lst->Freeze();
lst->ClearAll();
CreateListColumns();
wxString group = id == tree->GetRootItem() ? _T("") : tree->GetItemText(id);
// add files belonging to group
int counter = 0;
for (int i = 0; i < m_RecsCount; ++i)
{
if (group.IsEmpty() || (!m_Recs[i].groups.IsEmpty() && m_Recs[i].groups.Index(group) != wxNOT_FOUND))
{
// filter
if (FilterRec(&m_Recs[i]))
{
AddRecordToList(&m_Recs[i]);
++counter;
}
}
}
lst->Thaw();
// select first item
lst->SetItemState(0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
}
void UpdateDlg::FillFileDetails(const wxListItem& id)
{
wxTextCtrl* txt = XRCCTRL(*this, "txtInfo", wxTextCtrl);
txt->Clear();
UpdateRec* cur = GetRecFromListView();
if (!cur)
{
txt->Clear();
EnableButtons();
return;
}
txt->AppendText(_("Name: ") + cur->name + _T("\n"));
// txt->AppendText(_("Server: ") + cur->remote_server + _T("\n"));
// txt->AppendText(_("File: ") + cur->remote_file + _T("\n"));
txt->AppendText(_("Version: ") + cur->version + _T("\n"));
txt->AppendText(_("Size: ") + cur->size + _T("\n"));
txt->AppendText(_("Date: ") + cur->date + _T("\n\n"));
txt->AppendText(_("Description: \n"));
txt->AppendText(cur->desc);
txt->SetSelection(0, 0);
txt->SetInsertionPoint(0);
}
void UpdateDlg::InternetUpdate(bool forceDownload)
{
UpdateStatus(_("Please wait..."));
m_HasUpdated = false;
m_Net.SetServer(GetCurrentServer());
EnableButtons(false);
forceDownload = forceDownload || !XRCCTRL(*this, "chkCache", wxCheckBox)->GetValue();
bool forceDownloadMirrors = forceDownload || !wxFileExists(GetMirrorsFilename());
if (forceDownloadMirrors)
{
if (!m_Net.DownloadFile(_T("mirrors.cfg"), GetMirrorsFilename()))
{
UpdateStatus(_("Error downloading list of mirrors"), 0, 0);
return;
}
else
{
FillServers();
m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
}
}
wxString config = GetConfFilename();
forceDownload = forceDownload || !wxFileExists(config);
if (forceDownload && !m_Net.DownloadFile(_T("webupdate.conf"), config))
{
UpdateStatus(_("Error downloading list of updates"), 0, 0);
return;
}
else
{
IniParser ini;
if (!ini.ParseFile(config))
{
UpdateStatus(_("Failed to retrieve the list of updates"), 0, 0);
return;
}
ini.Sort();
if (m_Recs)
delete[] m_Recs;
// remember to delete[] m_Recs when we 're done with it!!!
// it's our responsibility once given to us
m_Recs = ReadConf(ini, &m_RecsCount, GetCurrentServer(), GetPackagePath());
FillGroups();
}
EnableButtons();
UpdateStatus(_("Ready"), 0, 0);
m_HasUpdated = true;
}
void UpdateDlg::FillServers()
{
wxComboBox* cmb = XRCCTRL(*this, "cmbServer", wxComboBox);
cmb->Clear();
m_Servers.Clear();
IniParser ini;
ini.ParseFile(GetMirrorsFilename());
int group = ini.FindGroupByName(_T("WebUpdate mirrors"));
for (int i = 0; group != -1 && i < ini.GetKeysCount(group); ++i)
{
cmb->Append(ini.GetKeyName(group, i));
m_Servers.Add(ini.GetKeyValue(group, i));
}
if (cmb->GetCount() == 0)
{
cmb->Append(_("devpaks.org Community Devpaks"));
m_Servers.Add(_T("http://devpaks.sourceforge.net/"));
}
cmb->SetSelection(0);
}
wxString UpdateDlg::GetConfFilename()
{
int server_hash = GetTextCRC32(GetCurrentServer().mb_str());
wxString config;
config.Printf(_T("%s/webupdate-%x.conf"), wxGetHomeDir().c_str(), server_hash);
return config;
}
wxString UpdateDlg::GetMirrorsFilename()
{
wxString config;
config << wxGetHomeDir() << _T("/mirrors.cfg");
return config;
}
wxString UpdateDlg::GetCurrentServer()
{
return m_Servers[XRCCTRL(*this, "cmbServer", wxComboBox)->GetSelection()];
}
wxString UpdateDlg::GetBasePath()
{
return g_MasterPath + wxFILE_SEP_PATH;
}
wxString UpdateDlg::GetPackagePath()
{
return GetBasePath() + _T("Packages") + wxFILE_SEP_PATH;
}
bool UpdateDlg::FilterRec(UpdateRec* rec)
{
if (!rec)
return false;
wxComboBox* cmb = XRCCTRL(*this, "cmbFilter", wxComboBox);
switch (cmb->GetSelection())
{
case 0: // All
return true;
case 1: // Installed
return rec->installed;
case 2: // installed with update available
return rec->installed && rec->version != rec->installed_version;
case 3: // downloaded but not installed
return rec->downloaded && !rec->installed;
case 4: // not installed
return !rec->downloaded && !rec->installed;
default:
return false;
}
return false; // doesn't reach here
}
void UpdateDlg::ApplyFilter()
{
wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
FillGroups();
FillFiles(tree->GetSelection());
EnableButtons();
}
UpdateRec* UpdateDlg::GetRecFromListView()
{
wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
int index = lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (index == -1)
return 0;
wxString title = lst->GetItemText(index);
return FindRecByTitle(title, m_Recs, m_RecsCount);
}
void UpdateDlg::DownloadFile(bool dontInstall)
{
UpdateStatus(_("Please wait..."));
UpdateRec* rec = GetRecFromListView();
if (!rec)
{
wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
UpdateStatus(_("Ready"), 0, 0);
return;
}
if (rec->version == rec->installed_version)
{
if (wxMessageBox(_("You seem to have installed the latest version.\nAre you sure you want to proceed?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO)
return;
}
if (!CreateDirRecursively(GetPackagePath()))
{
wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
return;
}
if (wxFileExists(GetPackagePath() + rec->local_file))
{
if (wxMessageBox(_("This file already exists!\nAre you sure you want to download it again?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO &&
rec->installable)
{
if (!dontInstall && wxMessageBox(_("Do you want to force-install it?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
InstallFile();
return;
}
}
m_Net.SetServer(rec->remote_server);
EnableButtons(false);
if (!m_Net.DownloadFile(rec->remote_file, GetPackagePath() + rec->local_file))
{
rec->downloaded = false;
UpdateStatus(_("Error downloading file: ") + rec->remote_server + _T(" > ") + rec->remote_file, 0, 0);
return;
}
else
rec->downloaded = true;
UpdateStatus(_("Ready"), 0, 0);
EnableButtons();
}
void UpdateDlg::InstallFile()
{
UpdateStatus(_("Please wait..."));
UpdateRec* rec = GetRecFromListView();
if (!rec)
{
wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
UpdateStatus(_("Ready"), 0, 0);
return;
}
wxYield();
if (rec->title == _T("WebUpdate Mirrors list"))
{
InstallMirrors(GetPackagePath() + rec->local_file);
rec->installed = true;
ApplyFilter();
UpdateStatus(_("Ready"), 0, 0);
return;
}
else if (!rec->installable)
{
UpdateStatus(_("Ready"), 0, 0);
return;
}
if (!CreateDirRecursively(GetPackagePath()))
{
UpdateStatus(_("Ready"), 0, 0);
wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
return;
}
wxArrayString files;
DevPakInstaller inst;
if (inst.Install(rec->name, GetPackagePath() + rec->local_file, GetBasePath(), &files))
{
// wxFileName fname(GetPackagePath() + rec->local_file);
// fname.SetExt("entry");
// fname.SetName(rec->title);
// CreateEntryFile(rec, fname.GetFullPath(), files);
CreateEntryFile(rec, GetPackagePath() + rec->entry, files);
wxMessageBox(_("DevPak installed"), _("Message"), wxICON_INFORMATION);
// refresh installed_version
rec->installed = true;
rec->installed_version = rec->version;
SetListColumnText(-1, 2, rec->installed_version);
}
else
{
wxMessageBox(_("DevPak was not installed.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
}
UpdateStatus(_("Ready"), 0, 0);
}
void UpdateDlg::InstallMirrors(const wxString& file)
{
wxLogNull ln;
if (!wxCopyFile(file, GetMirrorsFilename(), true))
wxMessageBox(_("Can't install mirrors file: ") + file, _("Error"), wxICON_ERROR);
else
{
wxRemoveFile(file);
FillServers();
m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
wxMessageBox(_("Mirrors installed"), _("Information"), wxICON_INFORMATION);
}
}
void UpdateDlg::UninstallFile()
{
UpdateStatus(_("Please wait..."));
UpdateRec* rec = GetRecFromListView();
if (!rec)
{
wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
UpdateStatus(_("Ready"), 0, 0);
return;
}
wxYield();
DevPakInstaller inst;
if (inst.Uninstall(GetPackagePath() + rec->entry))
{
wxMessageBox(_("DevPak uninstalled"), _("Message"), wxICON_INFORMATION);
// refresh installed_version
rec->installed_version.Clear();
rec->installed = false;
SetListColumnText(-1, 2, rec->installed_version);
}
else
{
wxMessageBox(_("DevPak was not uninstalled.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
}
}
void UpdateDlg::CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files)
{
wxString entry;
entry << _T("[Setup]\n");
entry << _T("AppName=") << rec->name << '\n';
entry << _T("AppVersion=") << rec->version << '\n';
// entry << _T("Description=") << rec->name << '\n';
entry << _T('\n');
entry << _T("[Files]\n");
for (unsigned int i = 0; i < files.GetCount(); ++i)
{
entry << files[i] << _T('\n');
}
wxFile f(filename, wxFile::write);
if (f.IsOpened())
{
f.Write(entry.c_str(), entry.Length());
}
}
void UpdateDlg::OnFileRightClick(wxListEvent& event)
{
// LOGSTREAM << "pt.x=" << event.GetPoint().x << ", pt.y=" << event.GetPoint().y << '\n';
UpdateRec* rec = GetRecFromListView();
if (!rec)
return;
wxMenu popup;
popup.Append(idPopupDownloadAndInstall, _("Download && install"));
popup.AppendSeparator();
popup.Append(idPopupDownload, _("Download"));
popup.Append(idPopupInstall, _("Install"));
popup.AppendSeparator();
popup.Append(idPopupUninstall, _("Uninstall"));
bool canDl = !rec->downloaded || rec->version != rec->installed_version;
bool canInst = rec->downloaded && (!rec->installed || rec->version != rec->installed_version);
popup.Enable(idPopupDownload, canDl);
popup.Enable(idPopupInstall, canInst);
popup.Enable(idPopupDownloadAndInstall, canInst || canDl);
popup.Enable(idPopupUninstall, rec->installed);
wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
lst->PopupMenu(&popup, event.GetPoint());
}
void UpdateDlg::OnFileDeSelected(wxListEvent& event)
{
wxListItem id;
FillFileDetails(id);
EnableButtons();
}
void UpdateDlg::OnFileSelected(wxListEvent& event)
{
FillFileDetails(event.GetItem());
EnableButtons();
}
void UpdateDlg::OnTreeSelChanged(wxTreeEvent& event)
{
FillFiles(event.GetItem());
EnableButtons();
}
void UpdateDlg::OnDownload(wxCommandEvent& event)
{
DownloadFile(true);
}
void UpdateDlg::OnInstall(wxCommandEvent& event)
{
InstallFile();
}
void UpdateDlg::OnUninstall(wxCommandEvent& event)
{
UninstallFile();
}
void UpdateDlg::OnDownloadAndInstall(wxCommandEvent& event)
{
DownloadFile();
}
void UpdateDlg::OnServerChange(wxCommandEvent& event)
{
InternetUpdate();
}
void UpdateDlg::OnFilterChange(wxCommandEvent& event)
{
ApplyFilter();
}
void UpdateDlg::OnConnect(wxCommandEvent& event)
{
XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Abort"));
EnableButtons();
}
void UpdateDlg::OnDisConnect(wxCommandEvent& event)
{
XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Close"));
EnableButtons();
}
void UpdateDlg::OnProgress(wxCommandEvent& event)
{
int prg = -1;
if (m_CurrFileSize != 0)
prg = event.GetInt() * 100 / m_CurrFileSize;
UpdateStatus(_("Downloading: ") + event.GetString(), prg);
wxStaticText* lbl = XRCCTRL(*this, "lblProgress", wxStaticText);
wxString msg;
msg.Printf(_("%s of %s"), GetSizeString(event.GetInt()).c_str(), GetSizeString(m_CurrFileSize).c_str());
lbl->SetLabel(msg);
}
void UpdateDlg::OnAborted(wxCommandEvent& event)
{
UpdateStatus(_("Download aborted: ") + event.GetString(), 0, 0);
XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
m_LastBlockSize = 0;
}
void UpdateDlg::OnDownloadStarted(wxCommandEvent& event)
{
m_CurrFileSize = event.GetInt();
UpdateStatus(_("Download started: ") + event.GetString(), 0, 100);
XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
m_LastBlockSize = 0;
}
void UpdateDlg::OnDownloadEnded(wxCommandEvent& event)
{
UpdateStatus(_("Download finished: ") + event.GetString());
XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
m_LastBlockSize = 0;
if (m_HasUpdated && event.GetInt() == 0)
{
UpdateRec* rec = GetRecFromListView();
if (rec)
{
if (rec->bytes != m_CurrFileSize)
wxMessageBox(_("File size mismatch for ") + event.GetString() + _("!\n\n"
"This, usually, means one of three things:\n"
"1) The reported size in the update list is wrong. The DevPak might still be valid.\n"
"2) The file's location returned a web error-page. Invalid DevPak...\n"
"3) The file is corrupt...\n\n"
"You can try to install it anyway. If it is not a valid DevPak, the operation will fail."),
_("Warning"), wxICON_WARNING);
}
if (rec && rec->installable && wxMessageBox(_("Do you want to install ") + event.GetString() + _(" now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
InstallFile();
else if (rec && rec->title == _T("WebUpdate Mirrors list"))
InstallMirrors(GetPackagePath() + rec->local_file);
}
m_CurrFileSize = 0;
}
void UpdateDlg::OnUpdateUI(wxUpdateUIEvent& event)
{
// hack to display the download message *after* the dialog has been shown...
if (m_FirstTimeCheck)
{
m_FirstTimeCheck = false; // no more, just once
wxString config = GetConfFilename();
if (wxFileExists(config))
InternetUpdate();
else
{
if (wxMessageBox(_("A list of updates needs to be downloaded.\nDo you want to do this now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
InternetUpdate(true);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1