mirror of
https://github.com/tuxbox-fork-migrations/recycled-ni-neutrino.git
synced 2025-08-30 17:01:08 +02:00
Replacing messagebox, hintbox_ext and some derivated parts with
basic class hintbox and derivated class CMsgBox. This should unify
window handling and avoids maintain of multiple classes with quasi
same purpose and adds more functionality.
TODO: fix and optimize details
Origin commit data
------------------
Commit: dde298b1b7
Author: Thilo Graf <dbt@novatux.de>
Date: 2016-04-04 (Mon, 04 Apr 2016)
1079 lines
31 KiB
C++
1079 lines
31 KiB
C++
/*
|
|
Based up Neutrino-GUI - Tuxbox-Project
|
|
Copyright (C) 2001 by Steffen Hehn 'McClean'
|
|
|
|
OPKG-Manager Class for Neutrino-GUI
|
|
|
|
Implementation:
|
|
Copyright (C) 2012-2015 T. Graf 'dbt'
|
|
www.dbox2-tuning.net
|
|
|
|
Adaptions:
|
|
Copyright (C) 2013 martii
|
|
gitorious.org/neutrino-mp/martiis-neutrino-mp
|
|
Copyright (C) 2015-2016 Stefan Seyfried
|
|
|
|
License: GPL
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
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/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <gui/opkg_manager.h>
|
|
|
|
#include <global.h>
|
|
#include <neutrino.h>
|
|
#include <neutrino_menue.h>
|
|
|
|
#include <gui/widget/icons.h>
|
|
#include <gui/widget/msgbox.h>
|
|
|
|
#include <gui/widget/progresswindow.h>
|
|
#include <gui/widget/hintbox.h>
|
|
#include <gui/widget/keyboard_input.h>
|
|
#include <driver/screen_max.h>
|
|
#include <gui/filebrowser.h>
|
|
#include <system/debug.h>
|
|
#include <system/helpers.h>
|
|
#include <unistd.h>
|
|
#include <sys/vfs.h>
|
|
#include <poll.h>
|
|
#include <fcntl.h>
|
|
#include <alloca.h>
|
|
#include <errno.h>
|
|
#include <sys/wait.h>
|
|
#include <fstream>
|
|
|
|
#if 0
|
|
#define OPKG_CL "opkg"
|
|
#else
|
|
#define OPKG_CL "opkg-cl"
|
|
#endif
|
|
|
|
#define OPKG_TMP_DIR "/tmp/.opkg"
|
|
#define OPKG_TEST_DIR OPKG_TMP_DIR "/test"
|
|
#define OPKG_CL_CONFIG_OPTIONS " -V2 --tmp-dir=/tmp --cache=" OPKG_TMP_DIR
|
|
|
|
#define OPKG_BAD_PATTERN_LIST_FILE CONFIGDIR "/bad_package_pattern.list"
|
|
#define OPKG_CONFIG_FILE "/etc/opkg/opkg.conf"
|
|
|
|
using namespace std;
|
|
|
|
enum
|
|
{
|
|
OM_LIST,
|
|
OM_LIST_INSTALLED,
|
|
OM_LIST_UPGRADEABLE,
|
|
OM_UPDATE,
|
|
OM_UPGRADE,
|
|
OM_REMOVE,
|
|
OM_INFO,
|
|
OM_INSTALL,
|
|
OM_STATUS,
|
|
OM_CONFIGURE,
|
|
OM_DOWNLOAD,
|
|
OM_MAX
|
|
};
|
|
|
|
static const string pkg_types[OM_MAX] =
|
|
{
|
|
OPKG_CL " list ",
|
|
OPKG_CL " list-installed ",
|
|
OPKG_CL " list-upgradable ",
|
|
OPKG_CL " -A update ",
|
|
OPKG_CL OPKG_CL_CONFIG_OPTIONS " upgrade ",
|
|
OPKG_CL OPKG_CL_CONFIG_OPTIONS " remove ",
|
|
OPKG_CL " info ",
|
|
OPKG_CL OPKG_CL_CONFIG_OPTIONS " install ",
|
|
OPKG_CL " status ",
|
|
OPKG_CL " configure ",
|
|
OPKG_CL " download "
|
|
};
|
|
|
|
COPKGManager::COPKGManager(): opkg_conf('\t')
|
|
{
|
|
init();
|
|
}
|
|
|
|
void COPKGManager::init()
|
|
{
|
|
if (!hasOpkgSupport())
|
|
return;
|
|
|
|
OM_ERRORS();
|
|
width = 80;
|
|
|
|
//define default dest keys
|
|
string dest_defaults[] = {"/", OPKG_TEST_DIR, OPKG_TMP_DIR, "/mnt"};
|
|
for(size_t i=0; i<sizeof(dest_defaults)/sizeof(dest_defaults[0]) ;i++)
|
|
config_dest.push_back(dest_defaults[i]);
|
|
|
|
loadConfig();
|
|
pkg_map.clear();
|
|
list_installed_done = false;
|
|
list_upgradeable_done = false;
|
|
expert_mode = false;
|
|
local_dir = &g_settings.update_dir_opkg;
|
|
v_bad_pattern = getBadPackagePatternList();
|
|
CFileHelpers::createDir(OPKG_TMP_DIR);
|
|
}
|
|
|
|
COPKGManager::~COPKGManager()
|
|
{
|
|
pkg_map.clear();
|
|
CFileHelpers::removeDir(OPKG_TMP_DIR);
|
|
}
|
|
|
|
int COPKGManager::exec(CMenuTarget* parent, const string &actionKey)
|
|
{
|
|
int res = menu_return::RETURN_REPAINT;
|
|
|
|
if (actionKey.empty()) {
|
|
if (parent)
|
|
parent->hide();
|
|
int ret = showMenu();
|
|
saveConfig();
|
|
CFileHelpers::removeDir(OPKG_TMP_DIR);
|
|
return ret;
|
|
}
|
|
int selected = menu->getSelected() - menu_offset;
|
|
|
|
if (expert_mode && actionKey == "rc_blue") {
|
|
if (selected < 0 || selected >= (int) pkg_vec.size() || !pkg_vec[selected]->installed)
|
|
return menu_return::RETURN_NONE;
|
|
|
|
char loc[200];
|
|
snprintf(loc, sizeof(loc), g_Locale->getText(LOCALE_OPKG_MESSAGEBOX_REMOVE), pkg_vec[selected]->name.c_str());
|
|
if (ShowMsg(LOCALE_OPKG_TITLE, loc, CMsgBox::mbrCancel, CMsgBox::mbYes | CMsgBox::mbCancel) != CMsgBox::mbrCancel) {
|
|
if (parent)
|
|
parent->hide();
|
|
execCmd(pkg_types[OM_REMOVE] + pkg_vec[selected]->name, CShellWindow::VERBOSE | CShellWindow::ACKNOWLEDGE_EVENT);
|
|
refreshMenu();
|
|
}
|
|
return res;
|
|
}
|
|
if (actionKey == "rc_info") {
|
|
if (selected < 0 || selected >= (int) pkg_vec.size()){
|
|
DisplayInfoMessage("No information available! Please first select a package!");
|
|
return menu_return::RETURN_NONE;
|
|
}
|
|
//show package info...
|
|
bool is_installed = pkg_vec[selected]->installed;
|
|
string infostr = getPkgInfo(pkg_vec[selected]->name, "", is_installed /*status or info*/);
|
|
|
|
//if available, generate a readable string for installation time
|
|
if (is_installed){
|
|
string tstr = getPkgInfo(pkg_vec[selected]->name, "Installed-Time", is_installed);
|
|
stringstream sstr(tstr);
|
|
time_t tval; sstr >> tval;
|
|
string newstr = asctime(localtime(&tval));
|
|
infostr = str_replace(tstr, newstr, infostr);
|
|
}
|
|
|
|
DisplayInfoMessage(infostr.c_str());
|
|
return res;
|
|
}
|
|
if (actionKey == "rc_yellow") {
|
|
expert_mode = !expert_mode;
|
|
updateMenu();
|
|
return res;
|
|
}
|
|
if (actionKey == "local_package") {
|
|
if (parent)
|
|
parent->hide();
|
|
|
|
CFileFilter fileFilter;
|
|
string filters[] = {"opk", "ipk"};
|
|
for(size_t i=0; i<sizeof(filters)/sizeof(filters[0]) ;i++)
|
|
fileFilter.addFilter(filters[i]);
|
|
|
|
CFileBrowser fileBrowser;
|
|
fileBrowser.Filter = &fileFilter;
|
|
|
|
if (fileBrowser.exec((*local_dir).c_str()))
|
|
{
|
|
string pkg_name = fileBrowser.getSelectedFile()->Name;
|
|
if (!installPackage(pkg_name))
|
|
showError(g_Locale->getText(LOCALE_OPKG_FAILURE_INSTALL), strerror(errno), pkg_name);
|
|
|
|
*local_dir = fileBrowser.getCurrentDir();
|
|
refreshMenu();
|
|
}
|
|
return res;
|
|
}
|
|
if(actionKey == pkg_types[OM_UPGRADE]) {
|
|
if (parent)
|
|
parent->hide();
|
|
int r = execCmd(actionKey, CShellWindow::VERBOSE | CShellWindow::ACKNOWLEDGE_EVENT);
|
|
if (r) {
|
|
showError(g_Locale->getText(LOCALE_OPKG_FAILURE_UPGRADE), strerror(errno), actionKey);
|
|
} else
|
|
installed = true;
|
|
refreshMenu();
|
|
/* I don't think ending up at the last package in the list is a good idea...
|
|
g_RCInput->postMsg((neutrino_msg_t) CRCInput::RC_up, 0);
|
|
*/
|
|
return res;
|
|
}
|
|
|
|
map<string, struct pkg>::iterator it = pkg_map.find(actionKey);
|
|
if (it != pkg_map.end()) {
|
|
if (parent)
|
|
parent->hide();
|
|
string force = "";
|
|
if (it->second.installed && !it->second.upgradable) {
|
|
char l[200];
|
|
snprintf(l, sizeof(l), g_Locale->getText(LOCALE_OPKG_MESSAGEBOX_REINSTALL), actionKey.c_str());
|
|
l[sizeof(l) - 1] = 0;
|
|
if (ShowMsg(LOCALE_OPKG_TITLE, l, CMsgBox::mbrCancel, CMsgBox::mbYes | CMsgBox::mbCancel) == CMsgBox::mbrCancel)
|
|
return res;
|
|
force = "--force-reinstall ";
|
|
}
|
|
|
|
//install package with size check ...cancel installation if check failed
|
|
installPackage(actionKey, force);
|
|
|
|
refreshMenu();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool COPKGManager::checkSize(const string& pkg_name)
|
|
{
|
|
string pkg_file = pkg_name;
|
|
string plain_pkg = getBaseName(pkg_file);
|
|
|
|
//exit check size if package already installed, because of auto remove of old stuff during installation
|
|
if (isInstalled(plain_pkg))
|
|
return true;
|
|
|
|
/* this is pretty broken right now for several reasons:
|
|
* space in /tmp is limited (/tmp being ramfs usually, but wasted
|
|
by unpacking the archive and then untaring it instead of using a pipe
|
|
* the file is downloaded for this test, then discarded and later
|
|
downloaded again for installation
|
|
so until a better solution is found, simply disable it. */
|
|
#if 0
|
|
//get available root fs size
|
|
//TODO: Check writability!
|
|
struct statfs root_fs;
|
|
statfs("/", &root_fs);
|
|
u_int64_t free_size = root_fs.f_bfree*root_fs.f_bsize;
|
|
|
|
/*
|
|
* To calculate the required size for installation here we make a quasi-dry run,
|
|
* it is a bit awkward, but relatively specific, other solutions are welcome.
|
|
* We create a temporary test directory and fill it with downloaded or user uploaded package file.
|
|
* Then we unpack the package and change into temporary testing directory.
|
|
* The required size results from the size of generated folders and subfolders.
|
|
* TODO: size of dependencies are not really considered
|
|
*/
|
|
CFileHelpers fh;
|
|
|
|
//create test pkg dir
|
|
string tmp_dest = OPKG_TEST_DIR;
|
|
tmp_dest += "/package";
|
|
fh.createDir(tmp_dest);
|
|
|
|
//change into test dir
|
|
chdir(OPKG_TEST_DIR);
|
|
|
|
//copy package into test dir
|
|
string tmp_dest_file = OPKG_TEST_DIR;
|
|
tmp_dest_file += "/" + plain_pkg;
|
|
if(!access( pkg_file.c_str(), F_OK)) //use local package
|
|
fh.copyFile(pkg_file.c_str(), tmp_dest_file.c_str(), 0644);
|
|
else
|
|
execCmd(pkg_types[OM_DOWNLOAD] + plain_pkg); //download package
|
|
|
|
//unpack package into test dir
|
|
string ar = "ar -x " + plain_pkg + char(0x2a);
|
|
execCmd(ar);
|
|
|
|
//untar package into test directory
|
|
string untar_tar_cmd = "tar -xf ";
|
|
untar_tar_cmd += OPKG_TEST_DIR;
|
|
untar_tar_cmd += "/data.tar.gz -C " + tmp_dest;
|
|
execCmd(untar_tar_cmd);
|
|
|
|
//get new current required minimal size from dry run test dir
|
|
u_int64_t req_size = fh.getDirSize(tmp_dest);
|
|
|
|
//clean up
|
|
fh.removeDir(OPKG_TEST_DIR);
|
|
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] Package: %s [required size=%" PRId64 " (free size: %" PRId64 ")]\n", __func__, __LINE__, pkg_name.c_str(), req_size, free_size);
|
|
if (free_size < req_size){
|
|
//exit if required size too much
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] WARNING: size check freesize=%" PRId64 " (recommended: %" PRId64 ")\n", __func__, __LINE__, free_size, req_size);
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
#define COPKGManagerFooterButtonCount 3
|
|
static const struct button_label COPKGManagerFooterButtons[COPKGManagerFooterButtonCount] = {
|
|
{ NEUTRINO_ICON_BUTTON_YELLOW, LOCALE_OPKG_BUTTON_EXPERT_ON },
|
|
{ NEUTRINO_ICON_BUTTON_INFO_SMALL, LOCALE_OPKG_BUTTON_INFO },
|
|
{ NEUTRINO_ICON_BUTTON_OKAY, LOCALE_OPKG_BUTTON_INSTALL }
|
|
};
|
|
#define COPKGManagerFooterButtonCountExpert 4
|
|
static const struct button_label COPKGManagerFooterButtonsExpert[COPKGManagerFooterButtonCountExpert] = {
|
|
{ NEUTRINO_ICON_BUTTON_YELLOW, LOCALE_OPKG_BUTTON_EXPERT_OFF },
|
|
{ NEUTRINO_ICON_BUTTON_INFO_SMALL, LOCALE_OPKG_BUTTON_INFO },
|
|
{ NEUTRINO_ICON_BUTTON_OKAY, LOCALE_OPKG_BUTTON_INSTALL },
|
|
{ NEUTRINO_ICON_BUTTON_BLUE, LOCALE_OPKG_BUTTON_UNINSTALL }
|
|
};
|
|
|
|
vector<string> COPKGManager::getBadPackagePatternList()
|
|
{
|
|
vector<string> v_ret;
|
|
|
|
ifstream in (OPKG_BAD_PATTERN_LIST_FILE, ios::in);
|
|
if (!in){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] can't open %s, %s\n", __func__, __LINE__, OPKG_BAD_PATTERN_LIST_FILE, strerror(errno));
|
|
return v_ret;
|
|
}
|
|
string line;
|
|
|
|
while(getline(in, line)){
|
|
v_ret.push_back(line);
|
|
}
|
|
in.close();
|
|
|
|
return v_ret;
|
|
}
|
|
|
|
bool COPKGManager::badpackage(std::string &s)
|
|
{
|
|
if(v_bad_pattern.empty())
|
|
return false;
|
|
|
|
size_t i;
|
|
string st = "";
|
|
for (i = 0; i < v_bad_pattern.size(); i++)
|
|
{
|
|
string p = v_bad_pattern[i];
|
|
if (p.empty())
|
|
continue;
|
|
|
|
size_t patlen = p.length() - 1;
|
|
bool res = false;
|
|
/* poor man's regex :-) only supported are "^" and "$" */
|
|
if (p.substr(patlen, 1) == "$") { /* match at end */
|
|
size_t pos = s.rfind(p.substr(0, patlen)); /* s.len-patlen can be -1 == npos */
|
|
if (pos != string::npos && pos == (s.length() - patlen))
|
|
res = true;
|
|
} else if (p.substr(0, 1) == "^") { /* match at beginning */
|
|
if (s.find(p.substr(1)) == 0)
|
|
res = true;
|
|
} else { /* match everywhere */
|
|
if (s.find(p) != string::npos)
|
|
res = true;
|
|
}
|
|
if (res)
|
|
st += p + " ";
|
|
}
|
|
|
|
if (!st.empty()){
|
|
dprintf(DEBUG_INFO, "[%s] filtered '%s' pattern(s) '%s'\n", __func__, s.c_str(), st.c_str());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void COPKGManager::updateMenu()
|
|
{
|
|
bool upgradesAvailable = false;
|
|
getPkgData(OM_LIST_INSTALLED);
|
|
getPkgData(OM_LIST_UPGRADEABLE);
|
|
for (map<string, struct pkg>::iterator it = pkg_map.begin(); it != pkg_map.end(); ++it) {
|
|
/* this should no longer trigger at all */
|
|
if (badpackage(it->second.name))
|
|
continue;
|
|
it->second.forwarder->iconName_Info_right = "";
|
|
it->second.forwarder->setActive(true);
|
|
if (it->second.upgradable) {
|
|
it->second.forwarder->iconName_Info_right = NEUTRINO_ICON_WARNING;
|
|
upgradesAvailable = true;
|
|
} else if (it->second.installed) {
|
|
it->second.forwarder->iconName_Info_right = NEUTRINO_ICON_CHECKMARK;
|
|
it->second.forwarder->setActive(expert_mode);
|
|
}
|
|
}
|
|
|
|
upgrade_forwarder->setActive(upgradesAvailable);
|
|
|
|
if (expert_mode){
|
|
menu->setFooter(COPKGManagerFooterButtonsExpert, COPKGManagerFooterButtonCountExpert);
|
|
}
|
|
else{
|
|
menu->setSelected(2); //back-item
|
|
menu->setFooter(COPKGManagerFooterButtons, COPKGManagerFooterButtonCount);
|
|
}
|
|
}
|
|
|
|
bool COPKGManager::checkUpdates(const std::string & package_name, bool show_progress)
|
|
{
|
|
if (!hasOpkgSupport())
|
|
return false;
|
|
|
|
doUpdate();
|
|
|
|
bool ret = false;
|
|
|
|
size_t i = 0;
|
|
CProgressWindow status;
|
|
status.showHeader(false);
|
|
|
|
if (show_progress){
|
|
status.paint();
|
|
status.showStatusMessageUTF(g_Locale->getText(LOCALE_OPKG_UPDATE_READING_LISTS));
|
|
status.showStatus(25); /* after do_update, we have actually done the hardest work already */
|
|
}
|
|
|
|
getPkgData(OM_LIST);
|
|
if (show_progress)
|
|
status.showStatus(50);
|
|
getPkgData(OM_LIST_UPGRADEABLE);
|
|
if (show_progress)
|
|
status.showStatus(75);
|
|
|
|
for (map<string, struct pkg>::iterator it = pkg_map.begin(); it != pkg_map.end(); ++it){
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] Update check for...%s\n", __func__, __LINE__, it->second.name.c_str());
|
|
if (show_progress){
|
|
/* showing the names only makes things *much* slower...
|
|
status.showStatusMessageUTF(it->second.name);
|
|
*/
|
|
status.showStatus(75 + 25*i / pkg_map.size());
|
|
}
|
|
|
|
if (it->second.upgradable){
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] Update packages available for...%s\n", __func__, __LINE__, it->second.name.c_str());
|
|
if (!package_name.empty() && package_name == it->second.name){
|
|
ret = true;
|
|
break;
|
|
}else
|
|
ret = true;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (show_progress){
|
|
status.showGlobalStatus(100);
|
|
status.showStatusMessageUTF(g_Locale->getText(LOCALE_FLASHUPDATE_READY)); // UTF-8
|
|
status.hide();
|
|
}
|
|
|
|
#if 0
|
|
pkg_map.clear();
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
int COPKGManager::doUpdate()
|
|
{
|
|
CHintBox *hb = new CHintBox(LOCALE_MESSAGEBOX_INFO, LOCALE_OPKG_UPDATE_CHECK);
|
|
hb->paint();
|
|
int r = execCmd(pkg_types[OM_UPDATE], CShellWindow::QUIET);
|
|
delete hb;
|
|
if (r) {
|
|
string msg = string(g_Locale->getText(LOCALE_OPKG_FAILURE_UPDATE));
|
|
msg += '\n' + tmp_str;
|
|
DisplayErrorMessage(msg.c_str());
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void COPKGManager::refreshMenu() {
|
|
list_installed_done = false,
|
|
list_upgradeable_done = false;
|
|
updateMenu();
|
|
}
|
|
|
|
int COPKGManager::showMenu()
|
|
{
|
|
installed = false;
|
|
if (checkUpdates())
|
|
DisplayInfoMessage(g_Locale->getText(LOCALE_OPKG_MESSAGEBOX_UPDATES_AVAILABLE));
|
|
|
|
#if 0
|
|
getPkgData(OM_LIST);
|
|
getPkgData(OM_LIST_UPGRADEABLE);
|
|
#endif
|
|
|
|
menu = new CMenuWidget(g_Locale->getText(LOCALE_SERVICEMENU_UPDATE), NEUTRINO_ICON_UPDATE, width, MN_WIDGET_ID_SOFTWAREUPDATE);
|
|
menu->addIntroItems(LOCALE_OPKG_TITLE, NONEXISTANT_LOCALE, CMenuWidget::BTN_TYPE_BACK, CMenuWidget::BRIEF_HINT_YES);
|
|
|
|
//upgrade all installed packages
|
|
upgrade_forwarder = new CMenuForwarder(LOCALE_OPKG_UPGRADE, true, NULL , this, pkg_types[OM_UPGRADE].c_str(), CRCInput::RC_red);
|
|
upgrade_forwarder->setHint(NEUTRINO_ICON_HINT_SW_UPDATE, LOCALE_MENU_HINT_OPKG_UPGRADE);
|
|
menu->addItem(upgrade_forwarder);
|
|
|
|
//select and install local package
|
|
CMenuForwarder *fw;
|
|
fw = new CMenuForwarder(LOCALE_OPKG_INSTALL_LOCAL_PACKAGE, true, NULL, this, "local_package", CRCInput::RC_green);
|
|
fw->setHint(NEUTRINO_ICON_HINT_SW_UPDATE, LOCALE_MENU_HINT_OPKG_INSTALL_LOCAL_PACKAGE);
|
|
menu->addItem(fw);
|
|
#if ENABLE_OPKG_GUI_FEED_SETUP
|
|
//feed setup
|
|
CMenuWidget feeds_menu(LOCALE_OPKG_TITLE, NEUTRINO_ICON_UPDATE, w_max (100, 10));
|
|
showMenuConfigFeed(&feeds_menu);
|
|
fw = new CMenuForwarder(LOCALE_OPKG_FEED_ADDRESSES, true, NULL, &feeds_menu, NULL, CRCInput::RC_www);
|
|
fw->setHint(NEUTRINO_ICON_HINT_SW_UPDATE, LOCALE_MENU_HINT_OPKG_FEED_ADDRESSES_EDIT);
|
|
menu->addItem(fw);
|
|
#endif
|
|
menu->addItem(GenericMenuSeparatorLine);
|
|
|
|
menu_offset = menu->getItemsCount();
|
|
|
|
menu->addKey(CRCInput::RC_info, this, "rc_info");
|
|
menu->addKey(CRCInput::RC_blue, this, "rc_blue");
|
|
menu->addKey(CRCInput::RC_yellow, this, "rc_yellow");
|
|
|
|
pkg_vec.clear();
|
|
for (map<string, struct pkg>::iterator it = pkg_map.begin(); it != pkg_map.end(); ++it) {
|
|
/* this should no longer trigger at all */
|
|
if (badpackage(it->second.name))
|
|
continue;
|
|
it->second.forwarder = new CMenuForwarder(it->second.desc, true, NULL , this, it->second.name.c_str());
|
|
it->second.forwarder->setHint("", it->second.desc);
|
|
menu->addItem(it->second.forwarder);
|
|
pkg_vec.push_back(&it->second);
|
|
}
|
|
|
|
updateMenu();
|
|
|
|
int res = menu->exec(NULL, "");
|
|
|
|
menu->hide ();
|
|
|
|
//handling after successful installation
|
|
string exit_action = "";
|
|
if (!has_err && installed){
|
|
/*!
|
|
Show a success message only if restart/reboot is required and user should decide what to do or not.
|
|
NOTE: marker file should be generated by opkg package itself (eg. with preinstall scripts),
|
|
so it's controlled by the package maintainer!
|
|
*/
|
|
//restart neutrino: user decision
|
|
if(!access( "/tmp/.restart", F_OK)){
|
|
int msg = ShowMsg(LOCALE_OPKG_TITLE, g_Locale->getText(LOCALE_OPKG_SUCCESS_INSTALL), CMsgBox::mbrNo,
|
|
CMsgBox::mbYes | CMsgBox::mbNo,
|
|
NEUTRINO_ICON_QUESTION,
|
|
width);
|
|
if (msg == CMsgBox::mbrYes)
|
|
exit_action = "restart";
|
|
}
|
|
//restart neutrino: forced
|
|
if (!access( "/tmp/.force_restart", F_OK))
|
|
exit_action = "restart";
|
|
//reboot stb: forced
|
|
if (!access( "/tmp/.reboot", F_OK)){
|
|
//ShowHint("", "Reboot ...", 300, 3); //TODO
|
|
g_RCInput->postMsg( NeutrinoMessages::REBOOT, 0);
|
|
res = menu_return::RETURN_EXIT_ALL;
|
|
}
|
|
}
|
|
/* remove the package-generated files... */
|
|
unlink("/tmp/.restart");
|
|
unlink("/tmp/.force_restart");
|
|
unlink("/tmp/.reboot");
|
|
|
|
delete menu;
|
|
|
|
if (!exit_action.empty())
|
|
CNeutrinoApp::getInstance()->exec(NULL, exit_action);
|
|
|
|
return res;
|
|
}
|
|
|
|
bool COPKGManager::hasOpkgSupport()
|
|
{
|
|
if (find_executable(OPKG_CL).empty()) {
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d]" OPKG_CL " executable not found\n", __func__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
/* If directory /var/lib/opkg resp. /opt/opkg
|
|
does not exist, it is created by opkg itself */
|
|
string deps[] = {"/var/lib/opkg", /*"/bin/opkg-check-config", "/bin/update-alternatives", "/share/opkg/intercept"*/};
|
|
for(size_t i=0; i<sizeof(deps)/sizeof(deps[0]) ;i++){
|
|
if(access(deps[i].c_str(), R_OK) !=0) {
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] %s not found\n", __func__, __LINE__, deps[i].c_str());
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
string COPKGManager::getInfoDir()
|
|
{
|
|
/* /opt/opkg/... is path in patched opkg, /var/lib/opkg/... is original path */
|
|
string dirs[] = {TARGET_PREFIX"/opt/opkg/info", TARGET_PREFIX"/var/lib/opkg/info"};
|
|
for (size_t i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) {
|
|
if (access(dirs[i].c_str(), R_OK) == 0)
|
|
return dirs[i];
|
|
}
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] InfoDir not found\n", __func__, __LINE__);
|
|
return "";
|
|
}
|
|
|
|
string COPKGManager::getPkgDescription(std::string pkgName, std::string pkgDesc)
|
|
{
|
|
static string infoPath;
|
|
if (infoPath.empty())
|
|
infoPath = getInfoDir();
|
|
if (infoPath.empty())
|
|
return pkgDesc;
|
|
|
|
string infoFile = infoPath + "/" + pkgName + ".control";
|
|
if (file_exists(infoFile.c_str())) {
|
|
FILE* fd = fopen(infoFile.c_str(), "r");
|
|
if (fd == NULL)
|
|
return pkgDesc;
|
|
|
|
fpos_t fz;
|
|
fseek(fd, 0, SEEK_END);
|
|
fgetpos(fd, &fz);
|
|
fseek(fd, 0, SEEK_SET);
|
|
if (fz.__pos == 0)
|
|
return pkgDesc;
|
|
|
|
char buf[512];
|
|
string package, version, description;
|
|
while (fgets(buf, sizeof(buf), fd)) {
|
|
if (buf[0] == ' ')
|
|
continue;
|
|
string line(buf);
|
|
trim(line, " ");
|
|
string tmp;
|
|
/* When pkgDesc is empty, return description only for OM_INFO */
|
|
if (!pkgDesc.empty()) {
|
|
tmp = getKeyInfo(line, "Package:", " ");
|
|
if (!tmp.empty())
|
|
package = tmp;
|
|
tmp = getKeyInfo(line, "Version:", " ");
|
|
if (!tmp.empty())
|
|
version = tmp;
|
|
}
|
|
tmp = getKeyInfo(line, "Description:", " ");
|
|
if (!tmp.empty())
|
|
description = tmp;
|
|
}
|
|
fclose(fd);
|
|
|
|
if (pkgDesc.empty())
|
|
return description;
|
|
|
|
string desc = package + " - " + version;
|
|
if (!description.empty())
|
|
desc += " - " + description;
|
|
|
|
return desc;
|
|
}
|
|
return pkgDesc;
|
|
}
|
|
|
|
void COPKGManager::getPkgData(const int pkg_content_id)
|
|
{
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] executing %s\n", __func__, __LINE__, pkg_types[pkg_content_id].c_str());
|
|
|
|
switch (pkg_content_id) {
|
|
case OM_LIST:
|
|
pkg_map.clear();
|
|
list_installed_done = false;
|
|
list_upgradeable_done = false;
|
|
break;
|
|
case OM_LIST_INSTALLED:
|
|
if (list_installed_done)
|
|
return;
|
|
list_installed_done = true;
|
|
for (map<string, struct pkg>::iterator it = pkg_map.begin(); it != pkg_map.end(); ++it)
|
|
it->second.installed = false;
|
|
break;
|
|
case OM_LIST_UPGRADEABLE:
|
|
if (list_upgradeable_done)
|
|
return;
|
|
list_upgradeable_done = true;
|
|
for (map<string, struct pkg>::iterator it = pkg_map.begin(); it != pkg_map.end(); ++it)
|
|
it->second.upgradable = false;
|
|
break;
|
|
}
|
|
|
|
pid_t pid = 0;
|
|
FILE *f = my_popen(pid, pkg_types[pkg_content_id].c_str(), "r");
|
|
if (!f) {
|
|
showError("Internal Error", strerror(errno), pkg_types[pkg_content_id]);
|
|
return;
|
|
}
|
|
|
|
char buf[256];
|
|
|
|
while (fgets(buf, sizeof(buf), f))
|
|
{
|
|
if (buf[0] == ' ')
|
|
continue; /* second, third, ... line of description will not be shown anyway */
|
|
std::string line(buf);
|
|
trim(line);
|
|
|
|
string name = getBlankPkgName(line);
|
|
if (name.empty())
|
|
continue;
|
|
|
|
switch (pkg_content_id) {
|
|
case OM_LIST: {
|
|
/* do not even put "bad" packages into the list to save memory */
|
|
if (badpackage(name))
|
|
continue;
|
|
pkg_map[name] = pkg(name, line, line);
|
|
map<string, struct pkg>::iterator it = pkg_map.find(name);
|
|
if (it != pkg_map.end())
|
|
it->second.desc = getPkgDescription(name, line);
|
|
break;
|
|
}
|
|
case OM_LIST_INSTALLED: {
|
|
map<string, struct pkg>::iterator it = pkg_map.find(name);
|
|
if (it != pkg_map.end())
|
|
it->second.installed = true;
|
|
break;
|
|
}
|
|
case OM_LIST_UPGRADEABLE: {
|
|
map<string, struct pkg>::iterator it = pkg_map.find(name);
|
|
if (it != pkg_map.end())
|
|
it->second.upgradable = true;
|
|
break;
|
|
}
|
|
default:
|
|
fprintf(stderr, "%s %s %d: unrecognized content id %d\n", __file__, __func__, __LINE__, pkg_content_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
string COPKGManager::getBlankPkgName(const string& line)
|
|
{
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] line: %s\n", __func__, __LINE__, line.c_str());
|
|
|
|
//check for error relevant contents and return an empty string if found
|
|
size_t pos0 = line.find("Collected errors:");
|
|
size_t pos01 = line.find(" * ");
|
|
if (pos0 != string::npos || pos01 != string::npos)
|
|
return "";
|
|
|
|
//split line and use name as return value
|
|
size_t pos1 = line.find(" ");
|
|
if (pos1 != string::npos)
|
|
return line.substr(0, pos1);
|
|
|
|
return "";
|
|
}
|
|
|
|
string COPKGManager::getPkgInfo(const string& pkg_name, const string& pkg_key, bool current_status)
|
|
{
|
|
execCmd(pkg_types[current_status ? OM_STATUS : OM_INFO] + pkg_name, CShellWindow::QUIET);
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] [data: %s]\n", __func__, __LINE__, tmp_str.c_str());
|
|
|
|
if (pkg_key.empty()) {
|
|
/* When description is empty, read data from InfoDir */
|
|
string tmp = getKeyInfo(tmp_str, "Description:", " ");
|
|
if (tmp.empty()) {
|
|
tmp = getPkgDescription(pkg_name);
|
|
if (!tmp.empty()) {
|
|
tmp_str += (string)"\nDescription: " + tmp + "\n";
|
|
}
|
|
}
|
|
return tmp_str;
|
|
}
|
|
|
|
return getKeyInfo(tmp_str, pkg_key, ":");
|
|
}
|
|
|
|
string COPKGManager::getKeyInfo(const string& input, const std::string& key, const string& delimiters)
|
|
{
|
|
string s = input;
|
|
size_t pos1 = s.find(key);
|
|
if (pos1 != string::npos){
|
|
size_t pos2 = s.find(delimiters, pos1)+ delimiters.length();
|
|
if (pos2 != string::npos){
|
|
size_t pos3 = s.find("\n", pos2);
|
|
if (pos3 != string::npos){
|
|
string ret = s.substr(pos2, pos3-pos2);
|
|
return trim(ret, " ");
|
|
}
|
|
else
|
|
dprintf(DEBUG_INFO, "[COPKGManager] [%s - %d] Error: [key: %s] missing end of line...\n", __func__, __LINE__, key.c_str());
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
int COPKGManager::execCmd(const char *cmdstr, int verbose_mode)
|
|
{
|
|
fprintf(stderr, "execCmd(%s)\n", cmdstr);
|
|
string cmd = string(cmdstr);
|
|
int res = 0;
|
|
has_err = false;
|
|
tmp_str.clear();
|
|
//bool ok = true;
|
|
|
|
//create CShellWindow object
|
|
CShellWindow shell(cmd, verbose_mode, &res, false);
|
|
|
|
//init slot for shell output handler with 3 args, no return value, and connect with loop handler inside of CShellWindow object
|
|
sigc::slot3<void, string*, int*, bool*> sl_shell;
|
|
sl_shell = sigc::mem_fun(*this, &COPKGManager::handleShellOutput);
|
|
shell.OnShellOutputLoop.connect(sl_shell);
|
|
#if 0
|
|
//demo for custom error message inside shell window loop
|
|
sigc::slot1<void, int*> sl1;
|
|
sl1 = sigc::mem_fun(*this, &COPKGManager::showErr);
|
|
shell.OnResultError.connect(sl1);
|
|
#endif
|
|
shell.exec();
|
|
|
|
return res;
|
|
}
|
|
|
|
void COPKGManager::handleShellOutput(string* cur_line, int* res, bool* ok)
|
|
{
|
|
//hold current res value
|
|
int _res = *res;
|
|
|
|
//use current line
|
|
string line = *cur_line;
|
|
|
|
//tmp_str contains all output lines and is available in the object scope of this
|
|
tmp_str += line + '\n';
|
|
|
|
//dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] come into shell handler with res: %d, line = %s\n", __func__, __LINE__, _res, line.c_str());
|
|
|
|
//detect any collected error
|
|
size_t pos2 = line.find("Collected errors:");
|
|
if (pos2 != string::npos)
|
|
has_err = true;
|
|
|
|
//check for collected errors and set res value
|
|
if (has_err){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] result: %s\n", __func__, __LINE__, line.c_str());
|
|
|
|
/*duplicate option cache: option is defined in OPKG_CL_CONFIG_OPTIONS,
|
|
* NOTE: if found first cache option in the opkg.conf file, this will be preferred and it's not really an error!
|
|
*/
|
|
if (line.find("Duplicate option cache") != string::npos){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] WARNING: %s\n", __func__, __LINE__, line.c_str());
|
|
*ok = true;
|
|
has_err = false;
|
|
*res = OM_SUCCESS;
|
|
return;
|
|
}
|
|
/*resolve_conffiles: already existent configfiles are not installed, but renamed in the same directory,
|
|
* NOTE: It's not fine but not really bad. Files should be installed separate or user can change manually
|
|
*/
|
|
if (line.find("Existing conffile") != string::npos){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] WARNING: %s\n", __func__, __LINE__, line.c_str());
|
|
*ok = true;
|
|
has_err = false;
|
|
*res = OM_SUCCESS;
|
|
return;
|
|
}
|
|
//download error:
|
|
if (line.find("opkg_download:") != string::npos){
|
|
*res = OM_DOWNLOAD_ERR;
|
|
*ok = false;
|
|
return;
|
|
}
|
|
//not enough space
|
|
if (line.find("No space left on device") != string::npos){
|
|
*res = OM_OUT_OF_SPACE_ERR;
|
|
*ok = false;
|
|
return;
|
|
}
|
|
//deps
|
|
if (line.find("satisfy_dependencies") != string::npos){
|
|
*res = OM_UNSATISFIED_DEPS_ERR;
|
|
*ok = false;
|
|
return;
|
|
}
|
|
//unknown error
|
|
if (*ok){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] ERROR: unhandled error %s\n", __func__, __LINE__, line.c_str());
|
|
*res = OM_UNKNOWN_ERR;
|
|
*ok = false;
|
|
return;
|
|
}
|
|
|
|
if (!has_err){
|
|
*ok = true;
|
|
*res = OM_SUCCESS;
|
|
}
|
|
}
|
|
|
|
*res = _res;
|
|
}
|
|
|
|
void COPKGManager::showErr(int* res)
|
|
{
|
|
string err = to_string(*res);
|
|
string errtest = err_list[1].id;
|
|
DisplayErrorMessage(errtest.c_str());
|
|
}
|
|
|
|
void COPKGManager::showError(const char* local_msg, char* err_message, const string& additional_text)
|
|
{
|
|
string msg = local_msg ? string(local_msg) + "\n" : "";
|
|
if (err_message)
|
|
msg += string(err_message) + ":\n";
|
|
if (!additional_text.empty())
|
|
msg += additional_text;
|
|
DisplayErrorMessage(msg.c_str());
|
|
}
|
|
|
|
bool COPKGManager::installPackage(const string& pkg_name, string options, bool force_configure)
|
|
{
|
|
//check package size...cancel installation if size check failed
|
|
if (!checkSize(pkg_name)){
|
|
DisplayErrorMessage(g_Locale->getText(LOCALE_OPKG_MESSAGEBOX_SIZE_ERROR));
|
|
}
|
|
else{
|
|
string opts = " " + options + " ";
|
|
|
|
int r = execCmd(pkg_types[OM_INSTALL] + opts + pkg_name, CShellWindow::VERBOSE | CShellWindow::ACKNOWLEDGE_EVENT | CShellWindow::ACKNOWLEDGE);
|
|
if (r){
|
|
switch(r){
|
|
case OM_OUT_OF_SPACE_ERR:
|
|
DisplayErrorMessage("Not enough space available");
|
|
break;
|
|
case OM_DOWNLOAD_ERR:
|
|
DisplayErrorMessage("Can't download package. Check network!");
|
|
break;
|
|
case OM_UNSATISFIED_DEPS_ERR:{
|
|
int msgRet = ShowMsg("Installation", "Unsatisfied deps while installation! Try to repeat to force dependencies!", CMsgBox::mbrCancel, CMsgBox::mbYes | CMsgBox::mbNo, NULL, 600, -1);
|
|
if (msgRet == CMsgBox::mbrYes)
|
|
return installPackage(pkg_name, "--force-depends");
|
|
break;
|
|
}
|
|
default:
|
|
showError(g_Locale->getText(LOCALE_OPKG_FAILURE_INSTALL), strerror(errno), pkg_types[OM_INSTALL] + opts + pkg_name);
|
|
}
|
|
}else{
|
|
if (force_configure)
|
|
execCmd(pkg_types[OM_CONFIGURE] + getBlankPkgName(pkg_name), 0);
|
|
installed = true; //TODO: catch real result
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool COPKGManager::isInstalled(const string& pkg_name)
|
|
{
|
|
string package = pkg_name;
|
|
package = getBaseName(package);
|
|
|
|
map<string, struct pkg>::iterator it = pkg_map.find(package);
|
|
if (it != pkg_map.end())
|
|
if (it->second.installed)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool COPKGManager::isUpgradable(const string& pkg_name)
|
|
{
|
|
string package = pkg_name;
|
|
package = getBaseName(package);
|
|
|
|
map<string, struct pkg>::iterator it = pkg_map.find(package);
|
|
if (it != pkg_map.end())
|
|
if (it->second.upgradable)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
void COPKGManager::showMenuConfigFeed(CMenuWidget *feed_menu)
|
|
{
|
|
feed_menu->addIntroItems(LOCALE_OPKG_FEED_ADDRESSES);
|
|
|
|
for(size_t i=0; i<OPKG_MAX_FEEDS ;i++){
|
|
CKeyboardInput *feedinput = new CKeyboardInput("Feed " +to_string(i+1), &config_src[i], 0, NULL, NULL, LOCALE_OPKG_ENTER_FEED_ADDRESS, LOCALE_OPKG_ENTER_FEED_ADDRESS_EXAMPLE);
|
|
CMenuForwarder *fw = new CMenuDForwarder( string(), true , config_src[i], feedinput, NULL, CRCInput::convertDigitToKey(i));
|
|
feed_menu->addItem( fw);
|
|
}
|
|
}
|
|
|
|
void COPKGManager::loadConfig()
|
|
{
|
|
opkg_conf.clear();
|
|
bool load_defaults = false;
|
|
|
|
if (!opkg_conf.loadConfig(OPKG_CONFIG_FILE, '\t')){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] Error: error while loading opkg config file! -> %s. Using default settings!\n", __func__, __LINE__, OPKG_CONFIG_FILE);
|
|
load_defaults = true;
|
|
}
|
|
|
|
//package feeds
|
|
for(size_t i=0; i<OPKG_MAX_FEEDS ;i++){
|
|
string src_key = "src " + to_string(i);
|
|
config_src[i] = opkg_conf.getString(src_key, string());
|
|
}
|
|
|
|
//dest dir default keys, predefined in constructor
|
|
for(size_t j=0; j<config_dest.size() ;j++){
|
|
string dest_key = "dest " + to_string(j);
|
|
opkg_conf.getString(dest_key, config_dest[j]);
|
|
}
|
|
|
|
//load default settings and write to config file
|
|
if (load_defaults)
|
|
saveConfig();
|
|
}
|
|
|
|
void COPKGManager::saveConfig()
|
|
{
|
|
//set package feeds
|
|
for(size_t i=0; i<OPKG_MAX_FEEDS ;i++){
|
|
string src_key = "src " + to_string(i);
|
|
|
|
if (!config_src[i].empty())
|
|
opkg_conf.setString(src_key, config_src[i]);
|
|
else
|
|
opkg_conf.deleteKey(src_key); //remove unused keys
|
|
}
|
|
|
|
//set dest dir default key values
|
|
for(size_t j=0; j<config_dest.size() ;j++){
|
|
string dest_key = "dest " + to_string(j);
|
|
opkg_conf.setString(dest_key, config_dest[j]);
|
|
}
|
|
|
|
//finally save config file
|
|
if (!opkg_conf.saveConfig(OPKG_CONFIG_FILE, '\t')){
|
|
dprintf(DEBUG_NORMAL, "[COPKGManager] [%s - %d] Error: error while saving opkg config file! -> %s\n", __func__, __LINE__, OPKG_CONFIG_FILE);
|
|
DisplayErrorMessage("Error while saving opkg config file!");
|
|
}
|
|
}
|