diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 25 | ||||
-rw-r--r-- | src/apt+http.protocol | 11 | ||||
-rw-r--r-- | src/apt-file.desktop | 8 | ||||
-rw-r--r-- | src/apt-files.desktop | 8 | ||||
-rw-r--r-- | src/apt-search.desktop | 8 | ||||
-rw-r--r-- | src/apt.cpp | 946 | ||||
-rw-r--r-- | src/apt.h | 137 | ||||
-rw-r--r-- | src/apt.protocol | 11 | ||||
-rw-r--r-- | src/aptcache.cpp | 292 | ||||
-rw-r--r-- | src/aptcache.h | 81 | ||||
-rw-r--r-- | src/debug.h | 17 | ||||
-rw-r--r-- | src/dpkg.cpp | 210 | ||||
-rw-r--r-- | src/dpkg.h | 50 | ||||
-rw-r--r-- | src/headerbg.png | bin | 0 -> 492 bytes | |||
-rw-r--r-- | src/kdedeb_logo.png | bin | 0 -> 10351 bytes | |||
-rw-r--r-- | src/kio_apt.css | 250 | ||||
-rw-r--r-- | src/packagemanager.cpp | 31 | ||||
-rw-r--r-- | src/packagemanager.h | 76 | ||||
-rw-r--r-- | src/parsers/Makefile.am | 7 | ||||
-rw-r--r-- | src/parsers/filesearch.cpp | 52 | ||||
-rw-r--r-- | src/parsers/list.cpp | 69 | ||||
-rw-r--r-- | src/parsers/parsers.cpp | 64 | ||||
-rw-r--r-- | src/parsers/parsers.h | 91 | ||||
-rw-r--r-- | src/parsers/policy.cpp | 202 | ||||
-rw-r--r-- | src/parsers/qhtmlstream.h | 286 | ||||
-rw-r--r-- | src/parsers/search.cpp | 76 | ||||
-rw-r--r-- | src/parsers/show.cpp | 194 | ||||
-rw-r--r-- | src/regexps.cpp | 45 | ||||
-rw-r--r-- | src/regexps.h | 23 |
29 files changed, 3270 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..ebd9d1f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,25 @@ +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kio_apt.la + +METASOURCES = AUTO + +kio_apt_la_SOURCES = aptcache.cpp apt.cpp regexps.cpp packagemanager.cpp dpkg.cpp + +kio_apt_la_LIBADD = $(top_builddir)/src/parsers/libparsers.la -lkio +kio_apt_la_LDFLAGS = -avoid-version -module $(all_libraries) $(KDE_PLUGIN) + +protocoldir = $(kde_servicesdir) +protocol_DATA = apt.protocol apt+http.protocol + +appdatadir = $(kde_datadir)/kio_apt/ +appdata_DATA = kio_apt.css kdedeb_logo.png + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kio_apt.pot + +noinst_HEADERS = debug.h aptcache.h apt.h regexps.h dpkg.h packagemanager.h + +searchproviderdir = $(kde_servicesdir)/searchproviders +searchprovider_DATA = apt-search.desktop apt-file.desktop apt-files.desktop +SUBDIRS = parsers diff --git a/src/apt+http.protocol b/src/apt+http.protocol new file mode 100644 index 0000000..bd3c25c --- /dev/null +++ b/src/apt+http.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_apt +protocol=apt+http +input=none +output=stream +reading=true +defaultMimetype=text/html +Icon=remote +Description=A kioslave for using apt-cache functionalities inside konqueror +determineMimetypeFromExtension=false +DocPath=kio_apt/index.html diff --git a/src/apt-file.desktop b/src/apt-file.desktop new file mode 100644 index 0000000..52891e0 --- /dev/null +++ b/src/apt-file.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Charset= +Hidden=false +Keys=apt-file +Name=APT - Find File +Query=apt:/fsearch?\\{@} +ServiceTypes=SearchProvider +Type=Service diff --git a/src/apt-files.desktop b/src/apt-files.desktop new file mode 100644 index 0000000..7089b96 --- /dev/null +++ b/src/apt-files.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Charset= +Hidden=false +Keys=apt-files +Name=APT - Filelist +Query=apt:/list?\\{@} +ServiceTypes=SearchProvider +Type=Service diff --git a/src/apt-search.desktop b/src/apt-search.desktop new file mode 100644 index 0000000..876337f --- /dev/null +++ b/src/apt-search.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Charset= +Hidden=false +Keys=apt-search +Name=APT - Search Package +Query=apt:/search?\\{@} +ServiceTypes=SearchProvider +Type=Service diff --git a/src/apt.cpp b/src/apt.cpp new file mode 100644 index 0000000..9a68991 --- /dev/null +++ b/src/apt.cpp @@ -0,0 +1,946 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * The forms are by Willy De la Court <[email protected]> * + * * + * 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" diff --git a/src/apt.h b/src/apt.h new file mode 100644 index 0000000..d18ffa8 --- /dev/null +++ b/src/apt.h @@ -0,0 +1,137 @@ +#ifndef KIOAPT_APTPROTOCOL_H +#define KIOAPT_APTPROTOCOL_H + +#include <qstring.h> +#include <qcstring.h> + +#include <kurl.h> +#include <kio/global.h> +#include <kio/slavebase.h> + +#include "debug.h" +#include "aptcache.h" +#include "packagemanager.h" + +#include <memory> + +class QCString; + +namespace Parsers +{ + class Parser; +} + +class AptProtocol : public QObject, public KIO::SlaveBase { + Q_OBJECT + + friend class AptCache; + + AptCache m_process; + PackageManager* m_adept_batch; + + typedef QMap<QString, QString> QueryOptions; + + /** This this the URL that should be used if we want + * to get the same output again. It is especially + * used in the make_html_form for the command link. + * + * The get() method sets it to apt:/command?query + * the various methods should add the relevant options, + * that is options which modify the query itself. + * The GUI-related options (like show_filelist in the + * show command) should NOT be added + */ + KURL m_query; + + /** if m_act is false, the ioslave is in browse-only + * mode (for forbidding install, for instance) */ + bool m_act, m_search, m_internal; + + QString m_stylesheet; + QString m_header_background; + QString m_logo; + QString m_logo_alt; + + +public: + AptProtocol( const QCString &pool_socket, const QCString &app_socket ); + virtual ~AptProtocol(); + virtual void mimetype( const KURL& url ); + virtual void get ( const KURL& url ); + + /** Sends the string to the ioslave's master + * SlaveBase::data() sends a byte array as is. Since we + * definitely don't want to send \0 to the master, + * we redefine data() for strings */ + void data(const QCString& string); + + /** @overload */ + void data(const QString& string); + + /** @overload */ + void data(const char* string); + + void data(const QByteArray& array); + + KURL buildURL(const QString& command, const QString& query) const; + KURL buildURL(const KURL& query) const; + + QString stylesheet() const; + QString header_background() const; + QString logo() const; + QString logo_alt() const; + +private slots: + void token_dispatch(const QString& tag, const QString& value); + +private: + std::auto_ptr<Parsers::Parser> m_parser; + + /** apt-cache search + * Performs apt-cache search, with the query encoded in url.query() + * and sends the result as an HTML file */ + void search( const QString& url, const QueryOptions& options ); + + /** apt-cache show + * Performs apt-cache search, with the package name encoded in url.query() + * and sends the result as an HTML file. + * It checks that the query contains a valid package name */ + void show( const QString& url, const QueryOptions& options ); + + /** apt-cache policy + * Performs apt-cache policy, with the package name encoded in url.query() + * and sends the result as an HTML file. + * It checks that the query contains a valid package name */ + void policy( const QString& url, const QueryOptions& options ); + + /** + * Sends an application/x-adept_batch file with commands + * understandable by adept_batch */ + void adept_batch( const QString& url, const QueryOptions& options ); + + /** + * Shows a form where one can enter parameters for some queries + **/ + void help(); + + /** Offline listing of the file of a package */ + bool can_listfiles(bool is_installed) const; + void listfiles( const QString& query, const QueryOptions& options); + + /** Offline file search. + * Searches the package which contains the specified file */ + bool can_searchfile(bool is_installed) const; + void searchfile( const QString& query, const QueryOptions& options); + + bool can_online(int mode) const; + void online( const QString& query, const QueryOptions& options); + + bool check_validpackage(const QString& query); + QString make_html_form() const; + QString make_html_tail(const QString& note = QString::null, bool with_form = true); + + KShellProcess * p; + +}; + +#endif diff --git a/src/apt.protocol b/src/apt.protocol new file mode 100644 index 0000000..24a197e --- /dev/null +++ b/src/apt.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_apt +protocol=apt +input=none +output=stream +reading=true +defaultMimetype=text/html +Icon=kioapt +Description=A kioslave for using apt-cache functionalities inside konqueror +determineMimetypeFromExtension=false +DocPath=kio_apt/index.html diff --git a/src/aptcache.cpp b/src/aptcache.cpp new file mode 100644 index 0000000..9511760 --- /dev/null +++ b/src/aptcache.cpp @@ -0,0 +1,292 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "aptcache.h" +#include "apt.h" +#include "debug.h" + +#include "regexps.h" + +#include <qstringlist.h> +#include <qregexp.h> + +#include <kdebug.h> + +#include <stdlib.h> +#include <signal.h> +#include <errno.h> + + +AptCache::AptCache() +{ + connect(&m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(receivedStdErr(KProcess*, char*, int ))); + connect(&m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(receivedStdOut(KProcess*, char*, int ))); +} +AptCache::~AptCache() {} + + + + +static QStringList received(QString& buffer, char* input, int input_len) +{ + buffer += QString::fromLatin1(input, input_len); + QStringList ret = QStringList::split('\n', buffer, true); + if (!buffer.endsWith("\n")) + { + buffer = ret.last(); + ret.pop_back(); + } + else + buffer = ""; + + return ret; +} +void AptCache::receivedStdErr( KProcess * /*process*/, char * buffer, int len ) +{ + static QRegExp rx_we("(W|E):\\s+(.*)"); + + QStringList lines = received(m_received_err, buffer, len); + for (QStringList::ConstIterator i = lines.begin(); i != lines.end(); ++i) + { + if (rx_we.exactMatch(*i)) + { + if (rx_we.cap(1) == "E") emit token("error", rx_we.cap(2)); + else emit token("warning", rx_we.cap(2)); + } + else + { + kdDebug() << "Unmatched error : " << *i << endl; + } + } +} +void AptCache::receivedStdOut( KProcess * /*process*/, char * buffer, int len ) +{ + QStringList lines = received(m_received_out, buffer, len); + (this->*m_receive)(lines); +} + + + + +void AptCache::clear() +{ + m_process.clearArguments(); + m_attribute = ""; + m_received_err = ""; + m_received_out = ""; +} + +bool AptCache::search(const QString& expression) +{ + clear(); + + m_process.setEnvironment("LANGUAGE", "C"); + m_process << "apt-cache" << "search"; + m_process << QStringList::split(" ", expression); + m_receive = &AptCache::receiveSearch; + return m_process.start(KProcess::Block, KProcess::Stdout ); +} + +void AptCache::receiveSearch(const QStringList& lines) +{ + static QRegExp rx_parse("([^ ]+) - (.*)"); + + QStringList::ConstIterator i; + for (i = lines.begin(); i != lines.end(); ++i) + { + if ((*i).isEmpty()) continue; + + if (!rx_parse.exactMatch(*i)) + { + kdDebug(DEBUG_ZONE) << "Parsing error. Line is " << *i << endl; + continue; + } + + emit token("package", rx_parse.cap(1)); + emit token("short_desc", rx_parse.cap(2)); + + kdDebug(DEBUG_ZONE) << "Found package : " << rx_parse.cap(1) << " - " << rx_parse.cap(2) << endl; + } +} + +bool AptCache::show(const QString& package) +{ + clear(); + + m_process.setEnvironment("LANGUAGE", "C"); + m_process << "apt-cache" << "show" << package; + m_receive = &AptCache::receiveShow; + return m_process.start(KProcess::Block, KProcess::Stdout ); +} + +void AptCache::receiveShow(const QStringList& lines) +{ + static bool pkgfield = false, insert_newline = false; + static int indent = 0; + + static QRegExp rx_attribute("([\\w-]+): (.*)"); + static const QString pkg_fields[] = + { "Suggests", "Replaces", "Depends", "Conflicts", QString::null }; + + QStringList::ConstIterator i; + for (i = lines.begin(); i != lines.end(); ++i) + { + QString data(*i); + if (data.isEmpty()) continue; + + if (rx_attribute.exactMatch(*i)) + { + m_attribute = rx_attribute.cap(1); + data = rx_attribute.cap(2); + + if (m_attribute != "Package") + emit token("field", m_attribute); + + insert_newline = pkgfield = false; + indent = 0; + + const QString * test_field; + for (test_field = pkg_fields; !test_field -> isNull(); ++test_field) + if (*test_field == m_attribute) + { + pkgfield = true; + break; + } + } + + if (m_attribute == "Package") + emit token("package", data); + else if (pkgfield) + parse_pkgfield(data); + else + { + int new_indent = data.find( QRegExp("[^\\s]") ); + + // new_indent > 0 means that we are in a multi-line + // field. Those lines always begin with " ", so we want + // to drop it. + if (new_indent > 0) --new_indent; + + if (new_indent != indent) + { + emit token("indent", QString::number(new_indent) ); + indent = new_indent; + insert_newline = false; + } + + if (data == " .") + { + if (insert_newline) + emit token("data", "\n"); + } + else + { + if (insert_newline) + emit token("data", "\n" + data); + else + emit token("data", data); + } + + insert_newline = true; + } + } +} + +void AptCache::parse_pkgfield(const QString& data) +{ + QStringList split(QStringList::split(",", data)); + for (QStringList::ConstIterator i = split.begin(); i != split.end(); ++i) + { + if (i != split.begin()) emit token("data", ", "); + + QStringList bar(QStringList::split("|", *i)); + for (QStringList::ConstIterator j = bar.begin(); j != bar.end(); ++j) + { + if (j != bar.begin()) emit token("data", " | "); + QString pkg, remaining; + + int paren = (*j).find('('); + if (paren != -1) + { + pkg = (*j).left(paren - 1); + remaining = (*j).right((*j).length() - paren + 1); + } + else + { + pkg = (*j); + } + + pkg = pkg.stripWhiteSpace(); + remaining = remaining.stripWhiteSpace(); + + emit token("package_link", pkg); + if (!remaining.isEmpty()) emit token("data", " " + remaining); + } + } +} + +bool AptCache::policy( const QString & package ) +{ + clear(); + + m_process.setEnvironment("LANGUAGE", "C"); + m_process << "apt-cache" << "policy" << package; + m_receive = &AptCache::receivePolicy; + return m_process.start(KProcess::Block, KProcess::Stdout ); +} + +void AptCache::receivePolicy(const QStringList& lines) +{ + static QRegExp rx_pkgname("(\\w[\\w+-.]+):"); + static QRegExp rx_location("^\\s*\\d+\\s[^\\d]"); + + for(QStringList::ConstIterator l = lines.begin(); l != lines.end(); ++l) + { + if ((*l).isEmpty()) continue; + + QString data( (*l).stripWhiteSpace() ); + if (rx_pkgname.exactMatch(*l)) + emit token("package", rx_pkgname.cap(1)); + else if (data.startsWith("Installed:", false)) + { + data = data.right(data.length() - 11); + emit token("installed", data); + m_installed = data; + } + else if (data.startsWith("Candidate:", false)) + { + data = data.right(data.length() - 11); + emit token("candidate", data); + m_candidate = data; + } + else if (data.startsWith("Version table:", false)) + emit token("version_table", QString::null); + else if (rx_location.search(data) > -1) + emit token("location", data); + else + { + if (data.startsWith("*** ")) + data = data.right( data.length() - 4 ); + + if (match_dversion(data.section(' ', 0, 0))) + emit token("version", data); + } + } +} + +QString AptCache::policy_installed() const +{ return m_installed; } +QString AptCache::policy_candidate() const +{ return m_candidate; } + + +#include "aptcache.moc" + diff --git a/src/aptcache.h b/src/aptcache.h new file mode 100644 index 0000000..c209507 --- /dev/null +++ b/src/aptcache.h @@ -0,0 +1,81 @@ +/*************************************************************************** +* Copyright (C) 2003 by Sylvain Joyeux * +* [email protected] * +* * +* 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. * +***************************************************************************/ +#ifndef KIOAPT_APTCACHE_H +#define KIOAPT_APTCACHE_H + +#include <qvaluelist.h> +#include <qmap.h> +#include <qstring.h> +#include <kprocess.h> + +class AptProtocol; + +/** +@author Sylvain Joyeux +*/ +class AptCache : public QObject { + Q_OBJECT + + typedef void (AptCache::*ReceiveMethod) (const QStringList& lines); + ReceiveMethod m_receive; + + // KProcIO messes the stderr and the stdout lines :( + KProcess m_process; + QString m_received_out, m_received_err; + + QString m_attribute; + + QString m_installed, m_candidate; + +private slots: + void receivedStdErr(KProcess* process, char* buffer, int len); + void receivedStdOut(KProcess* process, char* buffer, int len); + +private: + void clear(); + void receiveSearch(const QStringList& lines); + void receiveShow(const QStringList& lines); + void receivePolicy(const QStringList& lines); + void parse_pkgfield(const QString& data); + +public: + AptCache(); + ~AptCache(); + + bool search(const QString& expression); + bool show(const QString& package); + bool policy(const QString& package); + + QString policy_installed() const; + QString policy_candidate() const; + +signals: + /** Tags: + * warning (warning text) + * error (error text) + * package (package_name) + * short_desc (description) + * field (field_name) + * package_link (package_name) + * data (plain_data) + * indent (value) + * policy + * installed (version) + * candidate (version) + * version_table + * version (version_desc) + * location (location_desc) + * file (file_name) [for dpkg] + * end + */ + void token(const QString& tag, const QString& value); +}; + +#endif diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..ad8151b --- /dev/null +++ b/src/debug.h @@ -0,0 +1,17 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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. * + ***************************************************************************/ + +#ifndef KIOAPT_DEBUG +#define KIOAPT_DEBUG + +#define DEBUG_ZONE 7000 + +#endif + diff --git a/src/dpkg.cpp b/src/dpkg.cpp new file mode 100644 index 0000000..0a95851 --- /dev/null +++ b/src/dpkg.cpp @@ -0,0 +1,210 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 <config.h> + +#include "dpkg.h" +#include "debug.h" + +#include <klocale.h> +#include <kdebug.h> + +#include <qstringlist.h> +#include <qregexp.h> + + + +Dpkg::Dpkg(QObject *parent, const char *name) + : PackageManager(parent, name) +{ + connect(&m_process, SIGNAL(readReady (KProcIO *)), this, SLOT(readReady(KProcIO*))); +} + +Dpkg::~Dpkg() +{ +} + +int Dpkg::capabilities( int query ) const +{ + if ( (query & SEARCH_FILE) && (query & OFFLINE) ) + return query | INSTALLED_ONLY; + if ( (query & LIST_FILES) && (query & OFFLINE) ) + return query | INSTALLED_ONLY; + if ( query & ONLINE ) + return query; + return NOT_SUPPORTED; +} + +void Dpkg::readReady(KProcIO*) +{ + bool partial; + + QString newline; + QStringList lines; + while(m_process.readln(newline, true, &partial) != -1) + { + if (partial) m_buffer += newline; + else + { + newline.truncate(newline.length()); + QString line(m_buffer + newline); + lines << line; + m_buffer = ""; + } + + } + + (this->*m_receive)(lines); +} + +bool Dpkg::search( const QString & file ) +{ + m_process.resetAll(); + m_buffer = QString::null; + + m_process.clearArguments(); + m_process << "dpkg" << "-S" << file; + m_receive = &Dpkg::receiveSearch; + return m_process.start(KProcess::Block, KProcess::Stdout ); +} + +void Dpkg::receiveSearch( const QStringList & line ) +{ + static QRegExp rx_notfound("dpkg: (.*) not found"); + // the format of the dpkg -S answer is + // package1[, package2[, package3...]]: file + for (QStringList::ConstIterator i = line.begin(); i != line.end(); ++i) + { + //kdDebug(DEBUG_ZONE) << *i << endl; + if ((*i).isEmpty()) continue; + if (rx_notfound.exactMatch(*i)) + { + emit token("error", i18n("%1 not found").arg(rx_notfound.cap(1))); + continue; + } + + int semicolon = (*i).find(':'); + if (semicolon == -1) + { + kdDebug(DEBUG_ZONE) << "receiveSearch unmatched line : " << *i << endl; + continue; + } + QStringList packages = QStringList::split(',', (*i).left(semicolon)); + QString file = (*i).right( (*i).length() - semicolon - 1 ); + + emit token("file", file.stripWhiteSpace()); + + for (QStringList::ConstIterator j = packages.begin(); j != packages.end(); ++j) + emit token("package", (*j).stripWhiteSpace()); + } +} + +bool Dpkg::list( const QString & package ) +{ + m_process.resetAll(); + m_buffer = QString::null; + + m_process.clearArguments(); + m_process << "dpkg" << "-L" << package; + m_receive = &Dpkg::receiveList; + return m_process.start(KProcess::Block, KProcess::Stdout ); +} + +void Dpkg::receiveList( const QStringList & line ) +{ + static QRegExp rx_notfound("Package (.*) is not installed"); + for (QStringList::ConstIterator i = line.begin(); i != line.end(); ++i) + { + if (rx_notfound.search(*i) > -1) + emit token("error", i18n("Package %1 is not installed").arg(rx_notfound.cap(1))); + else if ((*i).startsWith("/")) + emit token("file", *i); + } +} + +static const QString + html_form_begin("\n<form action=\"http://packages.ubuntu.com/cgi-bin/search_contents.pl\" method=\"GET\">\n" + "<table class=\"query\">\n"); + +static QString make_title(const QString& title) +{ return "\t<tr><td class=\"title\" colspan=\"2\">" + title + "</td></tr>\n"; } +static const QString + html_form_end("<tr>\n" + "\t<td class=\"button\" colspan=\"2\">\n" + "\t\t<input type=\"submit\" value=\"%1\">\n" + "\t\t<input type=\"hidden\" name=\"searchmode\" value=\"searchfilesanddirs\">\n" + "\t\t<input type=\"hidden\" name=\"case\" value=\"insensitive\">\n" + "\t</td>\n" + "</tr>\n" + "</table>\n" + "</form>\n"); + +static const QString + html_form_line_begin("<tr>\n" + "\t<td><label for=\"%1\">%2</label></td>\n" + "\t<td>\n"); +static const QString + html_form_line_end("</td>\n</tr>\n"); + +static const QString html_form_combo("<select name=\"%1\" id=\"%2\">"); + +static QString make_form_text(const QString& type, const QString& label) +{ + return + html_form_line_begin.arg(type).arg(label) + + QString("<input type=\"text\" name=\"%1\" id=\"%2\">").arg(type).arg(type) + + html_form_line_end; +} + +static QString begin_form_combo(const QString& type, const QString& label) +{ + return + html_form_line_begin.arg(type).arg(label) + + QString("\t<select name=\"%1\" id=\"%2\">\n").arg(type).arg(type); +} +static QString make_form_option(const QString& name, const QString& text) +{ return "\t\t<option value=" + name + ">" + text + "</option>\n"; } +static QString end_form_combo() +{ return "\t</select>\n\t</td>\n</tr>\n"; } + +QString Dpkg::getOnlineForm() +{ + QString buffer; + QTextOStream stream(&buffer); + stream + << html_form_begin + << make_title( i18n("packages.ubuntu.com")) + + << make_form_text("word", i18n("File to search")) + << begin_form_combo("arch", i18n("Architecture")) + << make_form_option("i386", i18n("Intel x86")) + << make_form_option("amd64", i18n("AMD64")) + << make_form_option("sparc", i18n("SPARC")) + << make_form_option("powerpc", i18n("PowerPC")) + << make_form_option("hppa", i18n("HP PA/RISC")) + << make_form_option("ia64", i18n("Intel IA-64")) + << end_form_combo() + << begin_form_combo("version", i18n("Version")) + << make_form_option("gutsy", "gutsy") + << make_form_option("feisty", "feisty") + << make_form_option("edgy", "edgy") + << make_form_option("dapper", "dapper") + << make_form_option("breezy", "breezy") + << make_form_option("hoary", "hoary") + << make_form_option("warty", "warty") + << end_form_combo() + + << html_form_end.arg(i18n("Go online!")); + + return buffer; +} + +#include "dpkg.moc" + diff --git a/src/dpkg.h b/src/dpkg.h new file mode 100644 index 0000000..1092386 --- /dev/null +++ b/src/dpkg.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef DPKG_H +#define DPKG_H + +#include <config.h> + +#include "packagemanager.h" +#include <kprocio.h> + +/** +@author Sylvain Joyeux +*/ +class Dpkg : public PackageManager +{ + Q_OBJECT + + typedef void (Dpkg::*ReceiveMethod) (const QStringList& lines); + ReceiveMethod m_receive; + + KProcIO m_process; + QString m_buffer; + +private slots: + void readReady(KProcIO* io); + +private: + void receiveSearch(const QStringList& line); + void receiveList(const QStringList& line); + +public: + Dpkg(QObject *parent = 0, const char *name = 0); + ~Dpkg(); + + virtual bool list(const QString& package); + virtual bool search(const QString& file); + + virtual int capabilities(int query) const; + + virtual QString getOnlineForm(); +}; + +#endif diff --git a/src/headerbg.png b/src/headerbg.png Binary files differnew file mode 100644 index 0000000..b72738a --- /dev/null +++ b/src/headerbg.png diff --git a/src/kdedeb_logo.png b/src/kdedeb_logo.png Binary files differnew file mode 100644 index 0000000..2489def --- /dev/null +++ b/src/kdedeb_logo.png diff --git a/src/kio_apt.css b/src/kio_apt.css new file mode 100644 index 0000000..4b13e26 --- /dev/null +++ b/src/kio_apt.css @@ -0,0 +1,250 @@ +/** This is the stylesheet for the + * HTML code produced by the apt:/ ioslave + * (c) 2003 Sylvain Joyeux <[email protected]> + * Provided under the GPL licenses */ + +BODY { + background-color: white; + color: black; +} + +table { margin-top: 1em; } +table.header { + margin-top: 0; + margin-bottom: 1em; + width: 90%; + font-weight: bold; +/* font-variant: small-caps; */ + font-size: x-large; + + color: #0855C5; + + /*vertical-align: middle;*/ + + height: 85px; + background-color: #FFFFFF; + background-image: url("headerbg.png"); +} + +table.header td.logo { + width: 92px; +} + +H2 { text-align: justify; } +table.version TR { padding-top: 1em; } +table.version TD { padding-top: 1em; vertical-align: top; } +table.version TD.attname { font-weight: bold; } + +.footer { + margin-top: 3em; + font-size: small; + font-weight: bold; + text-align: center; +} + +td.header-title { + vertical-align: top; + text-align: center; + padding-top: 1.5em; + font-size: 15pt; +} +td.links { + vertical-align: bottom; +} + +/* global lind buttons at bottom of decorative header */ +table.links { + float: right; + border:0; + padding: 1px 0 0 0; + margin: 0; +} +table.links td a { + color: white +} +table.links td:hover { + background-color:#0C4293; +} +table.links td a:hover { + color: white; + background-color:#0C4293 +} +table.links td { + border-left: 1px solid #4A81D5; + padding: 0px 12px 0px 12px; + background-color:#0E4EAF; + font-size:9pt; + margin: 0; + /*font-weight: bold;*/ +} +/****************************************** +* Forms +*/ + +table.queryform { + margin: auto; + width: 80%; +} +table.query { + margin: auto; + margin-top: 1em; +} +table.queryform form { + margin: 2em; +} +table.query td.button { + text-align: center; + padding-top: .7em; +} + +table.query td.title { + text-align: center; + font-weight: bold; + padding-bottom: .7em; +} + +table.query td.button input[type="submit"] { + width: 40%; +} + +/****************************************** +* Show +*/ + +.error { + text-align: center; + font-weight: bold; + font-size: larger; + margin-top: 1em; +} + +/*h3.version, h3.installed-version { + padding-left: 1em; + margin-top: 4em; + text-align: left; + background-color: #0855C5; + color: white; +} +h3.installed-version { + background-color: #FF3333; +}*/ + +.version-header, .version-header-installed { + font-size: larger; + font-weight: bold; + /*padding-left: 1em;*/ + margin-top: 4em; + text-align: left; + color: white; + background-color: #0855C5; +} + +.version-header-installed { + background-color: #F03333; +} + +.version-header .links, .version-header-installed .links { + float: left; + + color: white; + + font-size:9pt; + background-color:#0C4293; + + padding: 0px 12px 0px 12px; + margin: 0 1em 0 0; + + border-left: 1px solid #4A81D5; +} + +.version-header-installed .links { + background-color: #CC2222; + border-left: 1px solid #FF7D7F; +} + +table.version { + margin-left: 3%; + width: 90%; + border-width: thin; + border-style: solid; +} + +.command +{ + margin-top: 1em; + margin-bottom: 1em; + text-align: center; + font-size: medium; +} +.filelist +{ + margin-left: 5%; +} + + +/*************************** +* Policy classes +*/ + +div.policy +{ margin-top: 1em; } +table.policy +{ margin: auto; } +table.policy TD +{ vertical-align: middle; } + +table.curver { + border: solid thin; + margin-left: auto; + margin-right: 2em; +} +.curver form p { + margin: 0; + padding: 0; +} + +.vtable { + border: solid thin; + margin-right: auto; + margin-left: 2em; + padding: .5em; +} +.vtable .header { + border-bottom: solid thin black; + text-align: center; + font-weight: bold; +} + + +.vtable-version { + font-weight: bold; + margin-right: 1em; +} +.vtable .version-pin { + font-weight: bold; +} +.vtable .location-pin { + font-weight: bold; + margin-left: 1em; + margin-right: 1em; +} + +table.curver TD, table.vtable TD +{ width: auto; + vertical-align: top; } + + +table.vtable thead td { + font-weight: bold; + text-align: center; + border-bottom: solid thin; +} + +table.curver form { + text-align: center; +} + +.pin +{ font-weight: bold; } +.file +{ font-weight: bold; } diff --git a/src/packagemanager.cpp b/src/packagemanager.cpp new file mode 100644 index 0000000..68dc35e --- /dev/null +++ b/src/packagemanager.cpp @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "packagemanager.h" + +PackageManager::PackageManager(QObject *parent, const char *name) + : QObject(parent, name) {} + + +PackageManager::~PackageManager() {} + +bool PackageManager::list( const QString & /*package*/ ) { return false; } +bool PackageManager::search( const QString & /*file*/ ) { return false; } + +int PackageManager::capabilities( int /*query*/ ) const { return 0; } + +QString PackageManager::getOnlineForm() +{ return QString::null; } +QString PackageManager::getOnlineURL + ( const QString& /* query */ + , const QMap<QString, QString>& /*options*/ ) +{ return QString::null; } + +#include "packagemanager.moc" + diff --git a/src/packagemanager.h b/src/packagemanager.h new file mode 100644 index 0000000..6d8d771 --- /dev/null +++ b/src/packagemanager.h @@ -0,0 +1,76 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef PACKAGEMANAGER_H +#define PACKAGEMANAGER_H + +#include <qobject.h> + +/** Base class for accessing package-manager specific + * functionalities. ATM, it supports listing files in a + * package and searching the package which owns a file. + * + * Support for online-search support is also included. + * + * @author Sylvain Joyeux + */ + +class PackageManager : public QObject +{ + Q_OBJECT +public: + PackageManager(QObject *parent = 0, const char *name = 0); + virtual ~PackageManager(); + + virtual bool search(const QString& file); + virtual bool list(const QString& package); + + /** Checks what the package manager is capable of. + * The \c cap parameter should be a or of exactly + * one function (SEARCH, LIST, ...) and one access + * (ONLINE, OFFLINE). + * The function returns NOT_SUPPORTED if the specified query is not + * supported. Otherwise, it returns cap with some + * restrictions (EXHAUSTIVE, INSTALLED_ONLY, ...) if + * they apply + * + * INSTALLED_ONLY meaning changes with the function considered. + * With SEARCH_FILE, it means that SEARCH_FILE only finds + * installed files. With LIST_FILES, it means that listing the + * files of a package is possible only if the package is already + * installed */ + virtual int capabilities(int query) const; + + virtual QString getOnlineForm(); + virtual QString getOnlineURL(const QString& query, const QMap<QString, QString>& options); + + enum Capabilities + { + NOT_SUPPORTED = 0, + + SEARCH_FILE = 0x01, + LIST_FILES = 0x02, + SHOW = 0x04, + OFFLINE = 0x10, + ONLINE = 0x20, + + INSTALLED_ONLY = 0x200 + }; + +signals: + /** Tags: + * warning (warning text) + * error (error text) + * file (file_name) [for dpkg] + * end + */ + void token(const QString& tag, const QString& value); +}; + +#endif diff --git a/src/parsers/Makefile.am b/src/parsers/Makefile.am new file mode 100644 index 0000000..9893dd4 --- /dev/null +++ b/src/parsers/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = $(all_includes) +METASOURCES = AUTO +libparsers_la_LDFLAGS = -avoid-version $(all_libraries) +noinst_LTLIBRARIES = libparsers.la +noinst_HEADERS = parsers.h qhtmlstream.h +libparsers_la_SOURCES = list.cpp search.cpp policy.cpp show.cpp parsers.cpp filesearch.cpp +libparsers_la_LIBADD = $(LIB_KIO) diff --git a/src/parsers/filesearch.cpp b/src/parsers/filesearch.cpp new file mode 100644 index 0000000..c0a7a36 --- /dev/null +++ b/src/parsers/filesearch.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "parsers.h" +#include "../apt.h" + +static QString + html_dpkgs_line_begin("<tr><td class=\"file\">%1</td><td>\n\t"), + html_dpkgs_line_end("\n</td></tr>\n"); + +namespace Parsers +{ + +void FileSearch::operator() (AptProtocol* slave, const QString & tag, const QString & value ) +{ + static QString buffer; + if (tag == "begin") + { + m_result_count = 0; + } + else if (tag == "error") + { + *slave << "<div class=\"error\">" + value + "</div>"; + } + else if (tag == "file") + { + if (m_result_count) + *slave << buffer + html_dpkgs_line_end; + + *slave << html_dpkgs_line_begin.arg(value); + ++m_result_count; + buffer = ""; + } + else if (tag == "package") + { + if (!buffer.isEmpty()) buffer = buffer + ", "; + buffer += "<a href=\"apt:/show?" + value + "\">" + value + "</a>"; + } + else if (tag == "end") + { + *slave << buffer + html_dpkgs_line_end; + buffer=""; + } +} + +} diff --git a/src/parsers/list.cpp b/src/parsers/list.cpp new file mode 100644 index 0000000..afaac7f --- /dev/null +++ b/src/parsers/list.cpp @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "parsers.h" +#include "../apt.h" + +#include <kio/slavebase.h> +#include <klocale.h> +#include <qregexp.h> +#include <kurl.h> + +namespace Parsers +{ + List::List(bool links) + : m_links(links) {} + + /** Parses the tokens sent by PackageManager::list */ + void List::operator() (AptProtocol* slave, const QString& tag, const QString& value ) + { + static QRegExp rx_manpage("/man/.*\\.\\d[^/]*$"); + + static QStringList files; + + if (tag == "begin") + { + m_result_count = 0; + } + else if (tag == "error") + { + *slave << "<div class=\"error\">" + value + "</div>"; + } + else if (tag == "file" && value != "/.") + { + if (m_links) + { + KURL url; + if (rx_manpage.search(value) >= 0) + url.setProtocol("man"); + else + url.setProtocol("file"); + + url.setPath(value); + + files += "<a href=\"" + url.htmlURL() + "\">" + value + "</a>"; + } + else + { + files += value; + } + + ++m_result_count; + } + else if (tag == "end") + { + files.sort(); + *slave << + "<div class=\"filelist\">\n" + files.join("\n<br>") + "\n</div>\n" + "<div class=\"footer\">" + i18n("%1 files in the package").arg(result_count()) + "</div>\n"; + files.clear(); + } + } +} diff --git a/src/parsers/parsers.cpp b/src/parsers/parsers.cpp new file mode 100644 index 0000000..2f959ce --- /dev/null +++ b/src/parsers/parsers.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "parsers.h" + +#include <kuserprofile.h> + +#include <kio/slavebase.h> +#include <qhtmlstream.h> +#include <qregexp.h> + +#include <stdlib.h> + +namespace Parsers +{ + static void print_czstring(KIO::SlaveBase& slave, const char* data, int len) + { + QByteArray nonull; + nonull.setRawData(data, len); + slave.data(nonull); + nonull.resetRawData(data, len); + } + void operator << (KIO::SlaveBase& slave, const QCString& string) + { print_czstring(slave, string.data(), string.size() - 1); } + void operator << (KIO::SlaveBase& slave, const QString& string) + { slave << string.utf8(); } + void operator << (KIO::SlaveBase& slave, const char* string) + { print_czstring(slave, string, strlen(string)); } + + + + Parser::Parser( ) {} + Parser::~Parser( ) {} + + void Parser::attribute_begin(QHtmlStream& stream, const QString& text) + { + stream + << block("tr") << endl + << block("td") << param("class") << "attname" << data() + << text + << close() << endl + << block("td"); + } + void Parser::attribute_end(QHtmlStream& stream) + { stream << close() << endl << close(); } + +// void Parser::operator ( )( KIO::SlaveBase * /*slave*/, +// const QString & /*tag*/, const QString & /*value*/ ) +// {} + + QString mangle_version(QString version) + { return "version_" + version.replace(QRegExp("[-:\\.\\+]"), QString("_")); } + +} + + + diff --git a/src/parsers/parsers.h b/src/parsers/parsers.h new file mode 100644 index 0000000..26e0113 --- /dev/null +++ b/src/parsers/parsers.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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. * + ***************************************************************************/ + +#ifndef PARSERS_H +#define PARSERS_H + +#include <qstring.h> + +namespace KIO +{ + class SlaveBase; +} +class QHtmlStream; +class AptProtocol; + +/** Here are defined the functional objects that takes the tokens + * sent by AptCache and PackageManager objects, and produces HTML */ +namespace Parsers +{ + class Parser + { + protected: + int m_result_count; + + static void attribute_begin(QHtmlStream& stream, const QString& text); + static void attribute_end(QHtmlStream& stream); + + public: + Parser(); + virtual ~Parser(); + int result_count() const { return m_result_count; } + virtual void operator () (AptProtocol* slave, const QString& tag, const QString& value) = 0; + }; + + class Search : public Parser + { + public: + void operator () (AptProtocol* slave, const QString& tag, const QString& value); + }; + + class List : public Parser + { + bool m_links; + public: + List(bool show_links); + void operator () (AptProtocol* slave, const QString& tag, const QString& value); + }; + + class FileSearch : public Parser + { + public: + void operator () (AptProtocol* slave, const QString& tag, const QString& value); + }; + + class Show : public Parser + { + QString m_package, m_installed; + bool m_act; + + public: + Show(const QString& package, const QString& installed, bool act); + void operator () (AptProtocol* slave, const QString& tag, const QString& value); + }; + + class Policy : public Parser + { + QString m_package, m_installed; + bool m_has_adept_batch; + bool m_act; + + public: + Policy(const QString& package, bool act); + QString getInstalled() const { return m_installed; } + void operator () (AptProtocol* slave, const QString& tag, const QString& value); + }; + + void operator << (KIO::SlaveBase& slave, const QCString& string); + void operator << (KIO::SlaveBase& slave, const QString& string); + void operator << (KIO::SlaveBase& slave, const char* string); + + QString mangle_version(QString version); +} + +#endif diff --git a/src/parsers/policy.cpp b/src/parsers/policy.cpp new file mode 100644 index 0000000..ad31b7f --- /dev/null +++ b/src/parsers/policy.cpp @@ -0,0 +1,202 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "parsers.h" +#include "../apt.h" + +#include <klocale.h> +#include <kdebug.h> +#include <qhtmlstream.h> +#include <qregexp.h> + +static void policy_begin(QHtmlStream& stream) +{ + stream + << block("div") << endl + << block("table", "policy") << endl + << block("tr") << block("td") << endl + << block("table", "curver") << endl + << block("tbody"); +} + +static void add_button(QHtmlStream& stream, const QString& mode, const QString& text, const QString& package) +{ + stream + << block("form") + << param("action") << "apt:/" + << param("method") << "get" << endl + << block("p") << endl + << tag("input") + << param("type") << "hidden" + << param("name") << "get" + << param("value") << mode << endl + << tag("input") + << param("type") << "hidden" + << param("name") << "package" + << param("value") << package << endl + << tag("input") + << param("type") << "submit" + << param("value") << text << endl + << close() + << close() << endl; +} + +static void add_version_link(QHtmlStream& stream, AptProtocol* slave, const QString& package, const QString& version) +{ + KURL url(slave->buildURL("show", package)); + url.setHTMLRef(Parsers::mangle_version(version)); + + stream + << block("a", "vtable-version") + << param("href") << url.htmlURL() + << data() << version + << close(); +} + +namespace Parsers +{ + +Policy::Policy(const QString& package, bool act) + : m_package(package), m_act(false) +{ + m_act = act; +} + +void Policy::operator() (AptProtocol* slave, const QString& type, const QString& value) +{ + static bool first_version = false, received_sth = false; + static QString buffer; + static QHtmlStream* stream; + + static QRegExp rx_notinstalled("(none)"); + + kdDebug() << "policy : " << type << " " << value << endl; + + if (type == "begin") + { + stream = new QHtmlStream(&buffer); + policy_begin(*stream); + } + else if (type == "installed") + { + received_sth = true; + + attribute_begin(*stream, i18n("Installed")); + if (rx_notinstalled.match(value) >= 0) + { + m_installed = QString::null; + *stream << i18n("no"); + } + else + { + m_installed = value.stripWhiteSpace(); + add_version_link(*stream, slave, m_package, m_installed); + } + attribute_end(*stream); + } + else if (type == "candidate") + { + received_sth = true; + + bool has_candidate = (rx_notinstalled.match(value) == -1); + + if (m_act && has_candidate) + { + *stream + << block("tr") << endl + << block("td") << param("colspan") << 2 << endl; + + if (m_installed.isEmpty()) + add_button(*stream, "install", i18n("Install"), m_package); + else + add_button(*stream, "remove", i18n("Remove"), m_package); + *stream << close() << close() << endl; + } + + attribute_begin(*stream, i18n("Candidate")); + if (has_candidate) + add_version_link(*stream, slave, m_package, value); + else + *stream << i18n("none"); + attribute_end(*stream); + + if (m_act && has_candidate + && !m_installed.isEmpty() && m_installed != value) + { + *stream + << block("tr") << endl + << block("td") << param("colspan") << 2 << endl; + add_button(*stream, "install", i18n("Upgrade"), m_package); + *stream << close() << close() << endl; + } + } + else if (type == "version_table") + { + received_sth = true; + + *stream + << close() << close() << endl // tbody, table, form + << close() << endl; // td + + first_version = true; + } + else if (type == "version") + { + QString version = value.section(' ', 0, 0); + QString pin = value.section(' ', 1, 1); + + if (first_version) + { + *stream + << block("td") << endl + << block("div", "vtable") << endl + << block("div", "header") << i18n("Version Table") << close() << endl + << block("div", "versions") << endl; + } /*else { + *stream << close() << close(); + }*/ + + QString version_link; + version_link = "<a href=\"apt:/show?" + m_package + "#" + mangle_version(version) + "\">" + + version + "</a>"; + + *stream << tag("br") << endl; + add_version_link(*stream, slave, m_package, version); + *stream << "[Pin " << block("span", "version-pin") << pin << close() << "]"; + + first_version = false; + } + else if (type == "location") + { + QStringList sections = QStringList::split(' ', value); + QString pin = sections.first(); + sections.pop_front(); + // remove the "Packages" field if it is here + if (sections.last() == "Packages") + sections.pop_back(); + + *stream << tag("br") << endl; + *stream << block("span", "location-pin") << pin << close() << sections.join(" ") << endl; + } + else if (type == "end") + { + if (received_sth) + { + *stream << close_all() << endl; + *slave << buffer; + } + + buffer = QString::null; + received_sth = false; + delete stream; + } +} + +} diff --git a/src/parsers/qhtmlstream.h b/src/parsers/qhtmlstream.h new file mode 100644 index 0000000..330f9d5 --- /dev/null +++ b/src/parsers/qhtmlstream.h @@ -0,0 +1,286 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef QHtmlStream_H +#define QHtmlStream_H + +#include <qtextstream.h> +#include <qstringlist.h> + +class QHtmlStream; +class QHtmlStreamManip; + +/** +@author Sylvain Joyeux +*/ + +class QHtmlStreamManip +{ +protected: + virtual void apply(QHtmlStream& stream) const = 0; + +public: + virtual ~QHtmlStreamManip() {}; + void operator () (QHtmlStream& stream) const + { apply(stream); } +}; + +class QHtmlStream +{ + QTextOStream m_stream; + + enum States + { + NORMAL_FLOW, + TAG, + BLOCK, + PARAM + }; + int m_state, m_enclosing_state; + + bool m_newline; + QString m_indent; + QStringList m_blockstack; + + void finalize_open() + { + if (m_state == PARAM) + m_state = m_enclosing_state; + + if (m_state == BLOCK) + m_stream << ">"; + else if (m_state == TAG) + m_stream << " />"; + + m_state = NORMAL_FLOW; + } + + void indent() + { + if (m_newline) + { + m_stream << m_indent; + m_newline = false; + } + } + + template<class T> + QHtmlStream& output(const T& o) + { + indent(); + + if (m_state == PARAM) + { + m_stream << "=\"" << o << "\""; + m_state = m_enclosing_state; + return *this; + } + + if (m_state == BLOCK) + { + m_stream << ">"; + m_state = NORMAL_FLOW; + } + else if (m_state == TAG) + { + m_stream << "/>"; + m_state = NORMAL_FLOW; + } + m_stream << o; + + return *this; + } + +public: + QHtmlStream(QString* buffer) + : m_stream(buffer), m_state(NORMAL_FLOW), m_newline(true) {} + ~QHtmlStream() {} + + void tag(const QString& name, const QString& cl, const QString& id) + { + finalize_open(); + indent(); + + m_stream << '<' << name; + m_state = TAG; + + if (!cl.isEmpty()) + m_stream << " class=\"" << cl << "\""; + if (!id.isEmpty()) + m_stream << " id=\"" << id << "\""; + } + + void block(const QString& name, const QString& cl, const QString& id) + { + finalize_open(); + indent(); + + m_stream << '<' << name; + m_indent += '\t'; + m_blockstack.push_front(name); + m_state = BLOCK; + + if (!cl.isEmpty()) + m_stream << " class=\"" << cl << "\""; + if (!id.isEmpty()) + m_stream << " id=\"" << id << "\""; + } + + void parameter(const QString& param_name) + { + if (m_state == NORMAL_FLOW) return; + + m_stream << " " << param_name; + m_enclosing_state = m_state; + m_state = PARAM; + } + + void close() + { + finalize_open(); + + m_indent.truncate(m_indent.length() - 1); + indent(); + m_stream << "</" << m_blockstack.first() << ">"; + m_blockstack.pop_front(); + } + void close_all(bool indent) + { + while( ! m_blockstack.empty() ) + { + if (indent) + (*this) << endl; + close(); + } + } + + void data() + { + finalize_open(); + } + + QHtmlStream & operator<< ( QChar c ) { return output(c); } + QHtmlStream & operator<< ( char c ) { return output(c); } + QHtmlStream & operator<< ( signed short i ) { return output(i); } + QHtmlStream & operator<< ( unsigned short i ) { return output(i); } + QHtmlStream & operator<< ( signed int i ) { return output(i); } + QHtmlStream & operator<< ( unsigned int i ) { return output(i); } + QHtmlStream & operator<< ( signed long i ) { return output(i); } + QHtmlStream & operator<< ( unsigned long i ) { return output(i); } + QHtmlStream & operator<< ( float f ) { return output(f); } + QHtmlStream & operator<< ( double f ) { return output(f); } + QHtmlStream & operator<< ( const char * s ) { return output(s); } + QHtmlStream & operator<< ( const QString & s ) { return output(s); } + QHtmlStream & operator<< ( const QCString & s ) { return output(s); } + + QHtmlStream & operator<< ( const QHtmlStreamManip& op ) + { + op(*this); + return *this; + } + + QHtmlStream & operator<< (QTSManip m) + { + finalize_open(); + m_stream << m; + return (*this); + } + + QHtmlStream & operator<< (QTSFUNC f) + { + finalize_open(); + int old_flags = m_stream.flags(); + m_stream << f; + if (old_flags == m_stream.flags()) + m_newline = true; + return (*this); + } +}; + +/*************************************************************************************** +* Stream manipulators +*/ + +class QHtmlStreamManip0 : public QHtmlStreamManip +{ +public: + typedef void (QHtmlStream::*Method)(); + +private: + Method m_method; + + void apply (QHtmlStream& stream) const + { (stream.*m_method)(); } + +public: + QHtmlStreamManip0(Method m) + : m_method(m) {} +}; + +class QHtmlStreamManip1 : public QHtmlStreamManip +{ +public: + typedef void (QHtmlStream::*Method)(const QString& param); + +private: + Method m_method; + QString m_param; + + void apply(QHtmlStream& stream) const + { (stream.*m_method)(m_param); } + +public: + QHtmlStreamManip1(Method m, const QString& param) + : m_method(m), m_param(param) {} +}; + +class QHtmlStreamManip3 : public QHtmlStreamManip +{ +public: + typedef void (QHtmlStream::*Method)(const QString& param0, const QString& param1, const QString& param2); + +private: + Method m_method; + QString m_param0, m_param1, m_param2; + + void apply(QHtmlStream& stream) const + { (stream.*m_method)(m_param0, m_param1, m_param2); } + +public: + QHtmlStreamManip3(Method m, const QString& param0, const QString& param1, const QString& param2) + : m_method(m), + m_param0(param0), m_param1(param1), m_param2(param2) {} +}; + +class CloseAll : public QHtmlStreamManip +{ +private: + bool m_indent; + void apply(QHtmlStream& stream) const + { stream.close_all(m_indent); } +public: + CloseAll(bool indent) : m_indent(indent) {} +}; + +inline QHtmlStreamManip3 tag(const QString& name, const QString& cl = QString::null, const QString& id = QString::null) +{ return QHtmlStreamManip3(&QHtmlStream::tag, name, cl, id); } +inline QHtmlStreamManip3 block(const QString& name, const QString& cl = QString::null, const QString& id = QString::null) +{ return QHtmlStreamManip3(&QHtmlStream::block, name, cl, id); } + +inline QHtmlStreamManip1 param(const QString& name) +{ return QHtmlStreamManip1(&QHtmlStream::parameter, name); } + +inline QHtmlStreamManip0 close() +{ return QHtmlStreamManip0(&QHtmlStream::close); } +inline QHtmlStreamManip0 data() +{ return QHtmlStreamManip0(&QHtmlStream::data); } +inline CloseAll close_all(bool indent = true) +{ return CloseAll(indent); } + +#endif diff --git a/src/parsers/search.cpp b/src/parsers/search.cpp new file mode 100644 index 0000000..0d0a952 --- /dev/null +++ b/src/parsers/search.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "parsers.h" +#include "../apt.h" + +#include "qhtmlstream.h" + +#include <kio/slavebase.h> + +namespace Parsers +{ +/** Parses the output of apt-cache search */ +void Search::operator() (AptProtocol* slave, const QString& tag, const QString& value) +{ + static QMap<QString, QString> results; + static QString cur_package; + static QString query; + + if (tag == "begin") + { + query = value; + m_result_count = 0; + } + else if (tag == "package") + { + ++m_result_count; + cur_package = value; + } + else if (tag == "short_desc") + { + results[cur_package] = value; + } + else if (tag == "end") + { + // We separate results whose package name matches the query + // and those who matches only with the description + QString normal, special; + QHtmlStream sstream(&special), nstream(&normal); + + // QMap iteration sorts wrt the key < operator + // with QStrings, it means case insensitive sort + QMap<QString, QString>::ConstIterator i; + for (i = results.begin(); i != results.end(); ++i) + { + const QString key = i.key(); + QHtmlStream* stream = &nstream; + if (key == query) + stream = &sstream; + + (*stream) + << block("tr") + << block("td") + << block("a") << param("href") << "apt:/show?" + key + << key + << close() + << close() << block("td") << *i << close() << endl + << close() << endl; + } + + if (!special.isEmpty()) + *slave << QString("<table>") + special + QString("</table>\n<hr>\n"); + *slave << QString("<table>") + normal + QString("</table>"); + + results.clear(); + } +} +} + diff --git a/src/parsers/show.cpp b/src/parsers/show.cpp new file mode 100644 index 0000000..0a1c503 --- /dev/null +++ b/src/parsers/show.cpp @@ -0,0 +1,194 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "parsers.h" +#include "../apt.h" + +#include <klocale.h> +#include <qregexp.h> + +static const QString + html_attribute_begin("<tr><td class=\"attname\">%1</td>\n\t<td>\n\t\t"), + html_attribute_classed("<tr class=\"%1\"><td class=\"attname\">%2</td>\n\t<td>\n\t\t"), + html_attribute_end("\n\t</td>\n</tr>\n"); + +// Converts the special chars in orig into its HTML equivalents +static QString text2html(const QString& orig) +{ QString ret(orig); + ret = ret.replace("<(?!a href)", "<"); + //ret = ret.replace( QRegExp("\n"), "<br />\n"); + return ret; +} + +static void close_indent(int indent, QString& buffer) +{ + if (buffer.isEmpty()) return; + if (indent) + buffer += "\n\t\t</div>"; +} +static void close_item(int indent, QString& buffer) +{ + if (buffer.isEmpty()) return; + close_indent(indent, buffer); + buffer += html_attribute_end; +} + +static void close_table(const QString& version, int indent, QString& buffer) +{ + if (buffer.isEmpty()) return; + close_item(indent, buffer); + buffer = version + "<table class=\"version\">\n" + buffer + "</table>\n"; +} + +static QString version_header_link(const KURL& url, const QString& name) +{ return QString("\t<a class=\"links\" href=\"" + url.htmlURL() + "\">" + name + "</a>\n"); } + +namespace Parsers +{ + +Show::Show(const QString& package, const QString& installed_version, bool act) + : m_package(package), m_installed(installed_version), m_act(false) +{ + m_act = act; +} + +void Show::operator() (AptProtocol* slave, const QString& tag, const QString& value) +{ + // Since we want to show the version first, we should wait for it + // before sending anything + static QString version; + static QString buffer; + static QString attribute; + static int indent; + static bool multiline = false; + static bool first_line = false, new_paragraph = true; + + if (tag == "begin" || tag == "package" || tag == "end") + { + if (multiline && !new_paragraph) + buffer += "</p>"; + + if (tag == "begin") + m_result_count = 0; + else + { + m_result_count += (tag == "package"); + + close_table(version, indent, buffer); + if (!buffer.isEmpty()) *slave << buffer; + } + + // init the state variables for the next package + buffer = ""; + indent = 0; + first_line = false; + new_paragraph = true; + } + else if (tag == "field") + { + if (multiline && !new_paragraph) + buffer += "</p>"; + + attribute = value; + if (value == "Depends" || value == "Description") + { + close_item(indent, buffer); + buffer += + html_attribute_begin.arg(value) + + " "; + + close_item(indent, buffer); + buffer += html_attribute_begin.arg(" "); + } + else if (value != "Version" && value != "Package") + { + close_item(indent, buffer); + buffer += html_attribute_begin.arg(value); + } + + if (value == "Description") + { + multiline = true; + new_paragraph = true; + first_line = true; + } + else + { + multiline = false; + } + } + else if (tag == "indent") + { + close_indent(indent, buffer); + + int new_indent = value.toInt(); + if (new_indent) + buffer += "\n\t<div style=\"margin-left: " + value + "em\">"; + indent = new_indent; + } + else if (tag == "data" && attribute == "Version") + { + KURL action ("apt:/get"); + + QString item_id = mangle_version(value); + if (value == m_installed) + { + action.setQuery("remove"); + version = QString("<div class=\"version-header-installed\" id=\"%1\">").arg(item_id) + + i18n("Installed version %1").arg(value); + } + else + { + action.setQuery("install"); + version = QString("<div class=\"version-header\" id=\"%1\">").arg(item_id) + + i18n("Version %1").arg(value); + } + + action.addQueryItem("package", m_package); + action.addQueryItem("version", value); + + if (m_act) + { + if (value == m_installed) + version += version_header_link(action, i18n("Remove")); + else + version += version_header_link(action, i18n("Install")); + } + version += "</div>\n"; + + } + else if (tag == "data") + { + if (multiline) + { + static const QRegExp rx_empty("^\\s*$"); + if (rx_empty.exactMatch(value)) + { + buffer += "</p>"; + new_paragraph = true; + } + else if (first_line) + { + new_paragraph = true; + first_line = false; + } + else if (new_paragraph) + { + buffer += "<p>"; + new_paragraph = false; + } + } + buffer += text2html(QString(value).replace(QRegExp("(http://\\S+)"),QString("<a href=\"\\1\">\\1</a>"))); + } + else if (tag == "package_link") + buffer += "<a href=\"" + slave->buildURL("show", value).htmlURL() + "\">" + value + "</a>"; +} + +} diff --git a/src/regexps.cpp b/src/regexps.cpp new file mode 100644 index 0000000..d49121d --- /dev/null +++ b/src/regexps.cpp @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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 "regexps.h" +#include "debug.h" + +#include <kdebug.h> + +bool match_dversion(QString version) +{ + static QRegExp rx_revision(rxs_revision); + QString allowed_vchars = ".+\\w"; + + kdDebug(DEBUG_ZONE) << version << endl; + if (version[1] == ':') + { + allowed_vchars += ":"; + if (! version[0].isDigit()) return false; + kdDebug(DEBUG_ZONE) << "Matched epoch" << endl; + version = version.right( version.length() - 2 ); + } + + kdDebug(DEBUG_ZONE) << version << endl; + int rev_pos = version.findRev('-'); + if (rev_pos > -1) + { + allowed_vchars += "-"; + QString revision = version.right( version.length() - rev_pos - 1); + if (! rx_revision.exactMatch(revision)) + return false; + + kdDebug(DEBUG_ZONE) << "Matched revision" << endl; + version.truncate( version.length() - rev_pos - 1 ); + } + + QRegExp rx_version("\\d[" + allowed_vchars + "]*"); + return rx_version.exactMatch(version); +} diff --git a/src/regexps.h b/src/regexps.h new file mode 100644 index 0000000..16f0e55 --- /dev/null +++ b/src/regexps.h @@ -0,0 +1,23 @@ +/*************************************************************************** + * Copyright (C) 2003 by Sylvain Joyeux * + * [email protected] * + * * + * 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. * + ***************************************************************************/ + +#ifndef REGEXPS_H +#define REGEXPS_H + +#include <qstring.h> +#include <qregexp.h> + +static const QString rxs_pkgname("\\w[\\w+-.]+"); +static const QString rxs_revision("\\w[.+\\w]*"); + +bool match_dversion(QString version); + +#endif + |