You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeio-apt/src/apt.cpp

947 lines
25 KiB

/***************************************************************************
* Copyright (C) 2003 by Sylvain Joyeux *
* sylvain.joyeux@m4x.org *
* The forms are by Willy De la Court <wdl@linux-lovers.be> *
* *
* 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. *
***************************************************************************/
#include "apt.h"
#include "regexps.h"
#include "dpkg.h"
#include "parsers/parsers.h"
#include <qcstring.h>
#include <kapplication.h>
#include <kinstance.h>
#include <kglobal.h>
#include <kconfig.h>
#include <kstandarddirs.h>
#include <klocale.h>
#include <kurl.h>
#include <kio/slavebase.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <qregexp.h>
#include <stdlib.h>
#include <config.h>
using namespace KIO;
/*************************************************************************
* Common definitions of HTML fragments
*/
static const QString
html_preamble("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\"\n"
"\t\"http://www.w3.org/TR/html4/strict.dtd\">\n"
"<html>\n");
static const QString
html_redirect(html_preamble +
QString("<head>\n"
"\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
"\t<meta http-equiv=\"Refresh\" content=\"0 ; URL=%1\">\n"
"</head>\n"
"\n<body></body>\n"
"</html>"));
static const QString
html_head(html_preamble +
QString("<head>\n"
"\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
"\t<link rel=\"stylesheet\" href=\"file:%1\">\n"
"\t<title>%2</title>\n"
"</head>\n\n"
"<body>\n"));
static QString close_html_head();
static QString open_html_head(const QString& title, bool links, AptProtocol const& config)
{
static const QString
html_head_table(
"<table class=\"header\" style=\"background-image: url(file:%1);\"\n"
"\t\tcellspacing=\"0\" cellpadding=\"0\">\n"
"<tr>\n"
"\t<td class=\"logo\" %2><img src=\"file:%3\" alt=\"%4\" style=\"border: 0px\" /></td>\n"
"\t<td class=\"header-title\">%4</td>\n");
QString rowspan;
if (links) rowspan = "rowspan=\"2\"";
QString ret =
html_head
.arg(config.stylesheet())
.arg(title)
+ html_head_table
.arg(config.header_background())
.arg(rowspan)
.arg(config.logo())
.arg(config.logo_alt())
.arg(title);
if (links)
{
return ret +
"</tr>\n"
"<tr>\n"
"\t<td class=\"links\">\n"
"\t<table class=\"links\" cellspacing=\"0\" cellpadding=\"0\">\n"
"\t<tr>\n";
}
else
{
return ret + "</tr>\n</table>\n\n";
}
}
static QString add_html_head_link(const QString& url, const QString& name, const QString& long_desc)
{
static const QString format("\t\t<td><a href=\"%1\" title=\"%2\">%3</a></td>\n");
return format.arg(url).arg(long_desc).arg(name);
}
static QString close_html_head()
{
return "\t</tr>\n"
"\t</table>\n"
"\t</td>\n"
"</tr>"
"</table>";
}
static const QString
html_tail("<div class=\"footer\">%1</div>\n"
"</body>\n"
"</html>");
QString AptProtocol::make_html_tail(const QString& note, bool with_form)
{
with_form = m_search && with_form;
QString ret;
if (with_form)
ret = "<hr>\n" + make_html_form();
if (!note.isEmpty())
ret += html_tail.arg(note + ". " + i18n("Page generated by kio_apt."));
else ret += html_tail.arg(i18n("Page generated by kio_apt."));
return ret;
}
/**********************************************************************************
* Search form
*/
static const QString
html_form_begin("\n<form action=\"apt:/\" method=\"GET\">\n"
"<table class=\"query\">\n");
static const QString
html_form_end("<tr>\n"
"\t<td class=\"button\" colspan=\"2\"><input type=\"submit\" value=\"%1\"></td>\n"
"</tr>\n"
"</table>\n"
"</form>\n");
static const QString
html_form_line("<tr>\n"
"\t<td><label for=\"%1\">%2</label></td>\n"
"\t<td><input type=\"text\" name=\"%3\" id=\"%4\"></td>\n"
"</tr>\n");
static QString make_html_form_line(const QString& type, const QString& label)
{ return html_form_line.arg(type).arg(label).arg(type).arg(type); }
static QString make_extform_cmd(bool ext_form, const KURL& query)
{
QString cmd = ext_form ? "0" : "1";
QString msg = ext_form ? i18n("Hide extended form") : i18n("Show extended form");
KURL url(query);
url.addQueryItem("extended_form", cmd);
url.setRef("extformcmd");
return
"<div class=\"command\" id=\"extformcmd\">\n"
"\t<a href=\"" + url.htmlURL() + "\">[" + msg + "]</a>\n"
"</div>\n";
}
/** Prints the HTML code for the query form */
QString AptProtocol::make_html_form() const
{
bool can_fsearch = false;
bool ext_form = KGlobal::config() -> readBoolEntry("extended_form", true);
// Only in config-file. Needed for some dpkg-based distros that are not Debian
can_fsearch = can_searchfile(true);
bool online = false;
bool online_form = KGlobal::config() -> readBoolEntry("online_form", true);
if (m_adept_batch)
online = online_form && (!m_internal) && ext_form && m_adept_batch -> capabilities(PackageManager::ONLINE);
QString ret;
QTextOStream stream(&ret);
stream << make_extform_cmd(ext_form, m_query);
if (online)
stream << "<table class=\"queryform\"><tr><td>\n";
stream << html_form_begin;
stream << "<tr><td colspan=\"2\" class=\"title\">" + i18n("Offline search") + "</td></tr>" << endl;
stream << make_html_form_line("search", i18n("Package search"));
if (ext_form)
{
if (can_fsearch)
stream << make_html_form_line("fsearch", i18n("File search"));
stream << make_html_form_line("show", i18n("Package info"));
}
stream << html_form_end.arg( i18n("Search") );
if (online)
{
stream << "\n</td><td>\n";
stream << m_adept_batch -> getOnlineForm();
stream << "\n</td></tr>\n</table>";
}
return ret;
}
/****************************************************************************************/
AptProtocol::AptProtocol( const QCString &pool_socket, const QCString &app_socket )
: SlaveBase( "kio_apt", pool_socket, app_socket ),
m_adept_batch(0), m_parser(0)
{
KStandardDirs* dirs = KGlobal::dirs();
m_stylesheet = dirs->findResource( "data", "kio_apt/kio_apt.css" );
m_logo = dirs->findResource( "data", "kio_apt/"
+ KGlobal::config() -> readEntryUntranslated("logo", "kdedeb_logo.png" ) );
m_header_background = dirs->findResource( "data", "kio_apt/"
+ KGlobal::config() -> readEntryUntranslated("background", "headerbg.png" ) );
m_logo_alt = KGlobal::config() -> readEntryUntranslated("alt_tag", i18n("KDE on Debian") );
connect(&m_process, SIGNAL(token(const QString&, const QString&)),
this, SLOT(token_dispatch(const QString&, const QString&)));
m_adept_batch = new Dpkg(this);
if (m_adept_batch)
{
connect(m_adept_batch, SIGNAL(token(const QString&, const QString&)),
this, SLOT(token_dispatch(const QString&, const QString&)));
}
}
AptProtocol::~AptProtocol() {}
QString AptProtocol::stylesheet() const { return m_stylesheet; }
QString AptProtocol::logo() const { return m_logo; }
QString AptProtocol::logo_alt() const { return m_logo_alt; }
QString AptProtocol::header_background() const { return m_header_background; }
void AptProtocol::token_dispatch(const QString& name, const QString& val)
{
if (m_parser.get())
(*m_parser)(this, name, val);
}
void AptProtocol::data(const QCString& string)
{
using namespace Parsers;
(*this) << string;
}
void AptProtocol::data(const QString& string)
{
using namespace Parsers;
(*this) << string;
}
void AptProtocol::data(const char* string)
{
using namespace Parsers;
(*this) << string;
}
void AptProtocol::data(const QByteArray& array)
{ SlaveBase::data(array); }
void AptProtocol::mimetype( const KURL & /*url*/ )
{
mimeType( "text/html" );
finished();
}
bool AptProtocol::check_validpackage(const QString& query)
{
static QRegExp rx_pkgname(rxs_pkgname);
if (!rx_pkgname.exactMatch(query))
{
error( ERR_SLAVE_DEFINED, i18n("\"%1\" is not a valid package name").arg(query) );
return false;
}
return true;
}
/********************************************************************
* Main entry point
*/
static QString read_option(QMap<QString, QString>& map, const QString& name, const QString& def)
{
if (!map.contains(name)) return def;
QString ret = map[name];
map.remove(name);
return ret;
}
void AptProtocol::get ( const KURL& url )
{
/* The queries have two possible formats :
- clean way to call a command
apt:/command?query&option=value&option=value&...
- needed to simplify forms
apt:/?command=query&command2=&command3=&option=value&option=value&...
- calls only the query form page
apt:/
*/
typedef void (AptProtocol::*Command)(const QString&, const QueryOptions&);
static const QString commands[] =
{ "search", "show", "policy",
"fsearch", "list", "online",
"get", QString::null };
static const Command methods[] =
{ &AptProtocol::search, &AptProtocol::show, &AptProtocol::policy,
&AptProtocol::searchfile, &AptProtocol::listfiles, &AptProtocol::online,
&AptProtocol::adept_batch };
QString command, query;
Command method = 0;
QueryOptions options = url.queryItems(KURL::CaseInsensitiveKeys);
// canonize the part before ? : remove the first /
QString path = url.path();
QString host = url.host();
if ( path.isEmpty() && !host.isEmpty() )
{
path = host;
}
if (path [0] == '/')
path = path.right(path.length() - 1);
for (int cmd_idx = 0; !commands[cmd_idx].isNull(); ++cmd_idx)
{
const QString cmd_it = commands[cmd_idx];
// Look if the command is in the path part
if (command.isEmpty() && cmd_it == path)
{
command = cmd_it;
method = methods[cmd_idx];
}
if (options.contains(cmd_it))
{
if (options[cmd_it].isEmpty() && !options[cmd_it].isNull())
{ // we have a &command=& format, we remove it
options.remove(cmd_it);
}
else if (command.isEmpty() && !options[cmd_it].isEmpty())
{ // the command is set in the options map
command = cmd_it;
method = methods[cmd_idx];
query = options[cmd_it];
options.remove(cmd_it);
}
}
}
// Well, we have no query for now, let's find it
if (query.isEmpty())
{
for (QueryOptions::Iterator i = options.begin(); i != options.end(); ++i)
{
if ((*i).isNull())
{
query = KURL::decode_string(i.key());
options.remove(i);
break;
}
}
}
// Interpret the ioslave config options
// and remove them from the options map
QString opt = read_option(options, "extended_form", QString::null);
if (!opt.isNull())
{
bool ext_form = (opt != "0");
KGlobal::config() -> writeEntry("extended_form", ext_form);
}
// Enable install/remove/upgrade/...
opt = read_option(options, "enable_actions", "1");
m_act = (opt != "0");
opt = read_option(options, "enable_search", "1");
m_search = (opt != "0");
// Allow links outside of apt:/ hierarchy
opt = read_option(options, "stay_internal", "0");
m_internal = (opt == "1");
// Sync the config (must use kcfg sometime :p)
KGlobal::config() -> sync();
if (command.isEmpty() || query.isEmpty())
{
//If path isn't empty, then a package is to be installed via an apt:/ link
if ( !path.isEmpty())
{
query = "install";
options["package"] = path;
options["weblinkinstall"] = 1;
adept_batch(query, options);
return;
}
// No query or no command, we go in help mode
m_query = buildURL(KURL("apt:/"));
help();
}
else
{
m_query = buildURL(command, query);
m_query.setHTMLRef(url.htmlRef());
for (QueryOptions::ConstIterator i = options.begin(); i != options.end(); ++i)
m_query.addQueryItem(i.key(), i.data());
kdDebug() << "Old url " << url << ", new url " << m_query << endl;
if (m_query != url)
{
redirection(m_query);
data(QByteArray());
finished();
return;
}
(this->*method)(query, options);
}
}
/***********************************************************************************
*
* form
*
*/
void AptProtocol::help()
{
mimeType("text/html");
QString buffer;
QTextOStream stream(&buffer);
stream
<< open_html_head(i18n("Search Form"), false, *this)
<< make_html_form()
<< make_html_tail(QString::null, false);
data(buffer);
data(QByteArray());
finished();
}
/***********************************************************************************
* apt-cache search
*/
void AptProtocol::search( const QString& query, const QueryOptions& /*options*/ )
{
mimeType("text/html");
data(open_html_head(i18n("Package search result for \"%1\"").arg(query), false, *this));
m_parser.reset(new Parsers::Search);
(*m_parser)(this, "begin", query);
if (!m_process.search( query ))
{
error(ERR_SLAVE_DEFINED, i18n("Error launching the search").arg(query));
return;
}
(*m_parser)(this, "end", QString::null);
data(make_html_tail( i18n("%1 results").arg(m_parser -> result_count())) );
data(QByteArray());
finished();
}
/***********************************************************************************
* apt-cache show
*/
static QString filelist_cmd(bool show_filelist, const KURL& query)
{
QString value = show_filelist ? "0" : "1";
QString msg = show_filelist ? i18n("Hide file list") : i18n("Show file list");
KURL url(query);
url.addQueryItem("show_filelist", value);
url.setRef("filelistcmd");
return
"<div class=\"command\" id=\"filelistcmd\">\n"
"\t<a href=\"" + url.htmlURL() + "\">"
"[" + msg + "]"
"</a>\n"
"</div>";
}
void AptProtocol::show(const QString& package, const QueryOptions& options)
{
if (!check_validpackage(package)) return;
if (options.contains("show_filelist"))
{
KGlobal::config() -> writeEntry("show_filelist", options["show_filelist"] != "0");
KGlobal::config() -> sync();
}
mimeType("text/html");
QString installed_version;
/** First, we parse policy
* We use here the fact that HTML is generated
* during the call of (*policy)(...,"end",...),
* since the header changes when the package
* is installed or not */
Parsers::Policy* policy = new Parsers::Policy(package, m_act);
m_parser.reset(policy);
(*m_parser)(this, "begin", QString::null);
{
if (!m_process.policy( package ))
{
error(ERR_SLAVE_DEFINED, i18n("Can't launch \"apt-cache policy %1\"").arg(package));
return;
}
installed_version = policy->getInstalled();
bool can_list = can_listfiles(!installed_version.isEmpty());
QString buffer;
QTextOStream s(&buffer);
if (can_list)
{
KURL url = buildURL("list", package);
s << open_html_head(i18n("Package description for \"%1\"").arg(package), true, *this)
<< add_html_head_link(url.htmlURL(), i18n("List package files"), "")
<< close_html_head();
}
else
{
s << open_html_head(i18n("Package description for \"%1\"").arg(package), false, *this);
}
data(buffer);
}
(*m_parser)(this, "end", QString::null);
/** Add package description section */
m_parser.reset(new Parsers::Show(package, installed_version, m_act));
(*m_parser)(this, "begin", QString::null);
{
if (!m_process.show(package))
{
error(ERR_SLAVE_DEFINED, i18n("Can't launch \"apt-cache show %1\"").arg(package));
return;
}
if (!m_parser -> result_count())
{
data("<div class=\"error\">" + i18n("No package found named \"%1\"").arg(package) + "</div>\n");
data(make_html_tail());
data(QByteArray());
finished();
return;
}
}
(*m_parser)(this, "end", QString::null);
/** Add file list (if enabled) */
bool show_filelist = KGlobal::config() -> readBoolEntry("show_filelist", false);
if ( show_filelist )
{
if (can_listfiles(!installed_version.isEmpty()))
{
data(
"<hr>\n"
+ filelist_cmd(show_filelist, m_query)
+ "<div class=\"filelist\">\n");
m_parser.reset(new Parsers::List(!m_internal));
(*m_parser)(this, "begin", QString::null);
if (!m_adept_batch -> list(package))
{
error(ERR_SLAVE_DEFINED, i18n("Error listing files of %1").arg(package));
return;
}
(*m_parser)(this, "end", QString::null);
data("\n</div>\n");
}
else // cannot list files
{
data(
"<hr>\n"
+ filelist_cmd(show_filelist, m_query)
+ "<div class=\"error\">" + i18n("Cannot list files for non-installed packages") + "</div>\n");
}
}
else
{
data("<hr>\n" + filelist_cmd(show_filelist, m_query));
}
data(make_html_tail());
data(QByteArray());
finished();
}
/***********************************************************************************
* apt-cache policy
*/
void AptProtocol::policy( const QString& query, const QueryOptions& /*options*/ )
{
if (!check_validpackage(query)) return;
mimeType("text/html");
data( open_html_head(i18n("Apt policy for \"%1\"").arg(query), false, *this) );
m_parser.reset(new Parsers::Policy(query, m_act));
(*m_parser)(this, "begin", QString::null);
if (!m_process.policy( query ))
{
error(ERR_SLAVE_DEFINED, i18n("Can't launch the policy for %1").arg(query));
return;
}
(*m_parser)(this, "end", QString::null);
data(make_html_tail());
data(QByteArray());
finished();
}
/***********************************************************************************
* Search the package which contains a specific file
*/
static const QString
html_dpkgs_begin("\n\n<table>\n"),
html_dpkgs_end("\n\n</table>\n");
bool AptProtocol::can_searchfile(bool is_installed) const
{
if (!m_adept_batch) return false;
int caps = m_adept_batch -> capabilities(PackageManager::SEARCH_FILE | PackageManager::OFFLINE);
if (!caps) return false;
return is_installed || !(caps & PackageManager::INSTALLED_ONLY);
}
void AptProtocol::searchfile(const QString& query, const QueryOptions& /*options*/)
{
if (!can_searchfile(true)) return;
mimeType("text/html");
data( open_html_head(i18n("File search for \"%1\"").arg(query), false, *this) + html_dpkgs_begin );
m_parser.reset(new Parsers::FileSearch);
(*m_parser)(this, "begin", QString::null);
if (!m_adept_batch -> search( query ))
{
error(ERR_SLAVE_DEFINED, i18n("Can't launch the package manager").arg(query));
return;
}
(*m_parser)(this, "end", QString::null);
data( html_dpkgs_end + make_html_tail(i18n("%1 files found").arg(m_parser -> result_count())) );
data(QByteArray());
finished();
}
/***********************************************************************************
* List the files of a package
*/
bool AptProtocol::can_listfiles(bool is_installed) const
{
if (!m_adept_batch) return false;
int caps = m_adept_batch -> capabilities(PackageManager::LIST_FILES | PackageManager::OFFLINE);
if (!caps) return false;
return is_installed || !(caps & PackageManager::INSTALLED_ONLY);
}
void AptProtocol::listfiles(const QString& query, const QueryOptions& /*options*/)
{
if (!can_listfiles(true)) return;
if (!check_validpackage(query)) return;
mimeType("text/html");
KURL ret_url = buildURL("show", query);
QString buffer;
QTextOStream stream(&buffer);
stream
<< open_html_head(i18n("Files in \"%1\"").arg(query), true, *this)
<< add_html_head_link(ret_url.htmlURL(), i18n("Show package info"), "")
<< close_html_head()
<< endl;
data(buffer);
m_parser.reset(new Parsers::List(!m_internal));
(*m_parser)(this, "begin", QString::null);
if (!m_adept_batch -> list( query ))
{
error(ERR_SLAVE_DEFINED, i18n("Can't launch the package manager").arg(query));
return;
}
(*m_parser)(this, "end", QString::null);
data(make_html_tail());
data(QByteArray());
finished();
}
/***********************************************************************************
* Go use online search services (like packages.debian.org for instance)
*/
//bool AptProtocol::can_online(int mode) const
//{
//// if (!m_adept_batch) return false;
//// return m_adept_batch -> capabilities(PackageManager::ONLINE | mode);
// return false;
//}
void AptProtocol::online(const QString& query, const QueryOptions& options)
{
QString url = m_adept_batch -> getOnlineURL(query, options);
redirection(url);
finished();
return;
}
/***********************************************************************************
* Send commands for adept_batch
*/
void AptProtocol::adept_batch(const QString& query, const QueryOptions& options)
{
p=NULL;
QString command;
QString url;
QStringList plist;
QStringList puninst;
QStringList pinst;
int pcount;
int ip;
if (query == "install" || query.isEmpty()) {
command = "kdesu adept_batch install ";
} else if (query == "remove") {
command = "kdesu adept_batch remove ";
}
if (command.isEmpty())
{
error(ERR_SLAVE_DEFINED, i18n("No package manager command specified"));
return;
}
if (!options.contains("package"))
{
error(ERR_SLAVE_DEFINED, i18n("No package specified"));
return;
}
plist = QStringList::split(", ", options["package"], false);
pcount = plist.count();
command += plist.join(" ");
if (pcount == 1)
{
if (query == "install")
ip = SlaveBase::messageBox(QuestionYesNo, i18n("Do you want to install %1 ?").arg(plist[0]), i18n("Package Installation"));
else
ip = SlaveBase::messageBox(QuestionYesNo, i18n("Do you want to remove %1 ?").arg(plist[0]), i18n("Package Removal"));
}
else
{
if (query == "install")
ip = SlaveBase::messageBox(QuestionYesNo,i18n("Do you want to install the following %1 packages ?\n%2").arg(pcount).arg(options["package"]));
else
ip = SlaveBase::messageBox(QuestionYesNo,i18n("Do you want to remove the following %1 packages ?\n").arg(pcount).arg(options["package"]));
}
kdDebug(DEBUG_ZONE) << command << endl;
if (ip == KMessageBox::Yes)
{
p = new KShellProcess;
p->clearArguments();
*p << command;
p->start( KProcess::Block, KProcess::All );
for(int i = 0; i != pcount; ++i)
{
QString installed_version;
Parsers::Policy* policy = new Parsers::Policy(plist[i], m_act);
m_parser.reset(policy);
(*m_parser)(this, "begin", QString::null);
{
if (!m_process.policy( plist[i] ))
{
error(ERR_SLAVE_DEFINED, i18n("Can't launch \"apt-cache policy %1\"").arg(plist[i]));
return;
}
installed_version = policy->getInstalled();
if (installed_version.isEmpty())
{
puninst += plist[i];
}
else
{
pinst += plist[i];
}
}
}
if (options.contains("weblinkinstall"))
{
if (puninst.count() == 0)
{
messageBox(Information,i18n("Installation successfull."));
}
else
{
QString toto = puninst.join(" ");
messageBox(Information,i18n("There was a problem installing %1.").arg(toto));
}
return;
}
else
{
url = "apt:/show?";
// Outside of a weblink, only one package can be installed at time
url += plist[0];
redirection(url);
data(QByteArray());
finished();
return;
}
}
else
{
return;
}
}
KURL AptProtocol::buildURL( const QString & command, const QString & query ) const
{
KURL url;
url.setProtocol("apt");
if (!command.startsWith("/"))
url.setPath("/" + command);
else
url.setPath(command);
url.setQuery(query);
return buildURL(url);
}
KURL AptProtocol::buildURL( const KURL& query ) const
{
KURL url(query);
if (!m_act)
url.addQueryItem("enable_actions", "0");
if (!m_search)
url.addQueryItem("enable_search", "0");
if (m_internal)
url.addQueryItem("stay_internal", "1");
return url;
}
/***********************************************************************************
*
* kdemain
*
*/
extern "C" {
int kdemain( int argc, char **argv ) {
KInstance instance( "kio_apt" );
if ( argc != 4 ) {
kdDebug( DEBUG_ZONE ) << "Usage: kio_apt protocol domain-socket1 domain-socket2" << endl;
exit ( -1 );
}
AptProtocol slave( argv[ 2 ], argv[ 3 ] );
slave.dispatchLoop();
return 0;
}
}
#include "apt.moc"