summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-02-03 14:50:26 +0100
committerEugen Wissner <belka@caraus.de>2026-02-03 14:50:26 +0100
commit97efcb4faa6de6a7a0ba19c483f3e69ef1fbf48d (patch)
treed1488b5b196e1c89191a2b06cfb79df1698ac42c
parentda02080fc1db3be9275832b7ad929165b1a38e3e (diff)
downloadkatja-97efcb4faa6de6a7a0ba19c483f3e69ef1fbf48d.tar.gz
Add currently existing PackageKit backend
-rw-r--r--backend/Slackware.conf.in15
-rw-r--r--backend/dl.cc288
-rw-r--r--backend/dl.h24
-rw-r--r--backend/job.cc104
-rw-r--r--backend/job.h19
-rw-r--r--backend/meson.build53
-rw-r--r--backend/metadata.dbbin0 -> 14336 bytes
-rw-r--r--backend/pk-backend-slack.cc1078
-rw-r--r--backend/pkgtools.cc175
-rw-r--r--backend/pkgtools.h36
-rw-r--r--backend/slackpkg.cc525
-rw-r--r--backend/slackpkg.h30
-rw-r--r--backend/tests/definitions.cc92
-rw-r--r--backend/tests/dl-test.cc25
-rw-r--r--backend/tests/job-test.cc39
-rw-r--r--backend/tests/meson.build55
-rw-r--r--backend/tests/slackpkg-test.cc25
-rw-r--r--backend/utils.cc231
-rw-r--r--backend/utils.h32
19 files changed, 2846 insertions, 0 deletions
diff --git a/backend/Slackware.conf.in b/backend/Slackware.conf.in
new file mode 100644
index 0000000..a1a6efa
--- /dev/null
+++ b/backend/Slackware.conf.in
@@ -0,0 +1,15 @@
+# It is a sample configuration.
+
+[slackware]
+Mirror=http://mirrors.slackware.com/slackware/@pkgmain@-14.2/
+Priority=patches;@pkgmain@;extra;pasture;testing
+#Blacklist=
+
+
+#[dropline]
+#Mirror=http://dl.flevum.de/slackware/3.10/
+#IndexFile=http://dl.flevum.de/slackware/3.10/DroplineFiles3.10
+
+#[dropline]
+#Mirror=http://dl.flevum.de/slackware64/3.10/
+#IndexFile=http://dl.flevum.de/slackware64/3.10/Dropline64Files3.10
diff --git a/backend/dl.cc b/backend/dl.cc
new file mode 100644
index 0000000..5a749c1
--- /dev/null
+++ b/backend/dl.cc
@@ -0,0 +1,288 @@
+#include <sqlite3.h>
+#include <stdlib.h>
+#include "dl.h"
+#include "utils.h"
+
+namespace slack {
+
+/**
+ * slack::Dl::collect_cache_info:
+ * @tmpl: temporary directory for downloading the files.
+ *
+ * Download files needed to get the information like the list of packages
+ * in available repositories, updates, package descriptions and so on.
+ *
+ * Returns: List of files needed for building the cache.
+ **/
+GSList *
+Dl::collect_cache_info (const gchar *tmpl) noexcept
+{
+ CURL *curl = NULL;
+ GSList *file_list = NULL;
+ GFile *tmp_dir, *repo_tmp_dir;
+
+ /* Create the temporary directory for the repository */
+ tmp_dir = g_file_new_for_path(tmpl);
+ repo_tmp_dir = g_file_get_child(tmp_dir, this->get_name ());
+ g_file_make_directory(repo_tmp_dir, NULL, NULL);
+
+ /* There is no ChangeLog yet to check if there are updates or not. Just mark the index file for download */
+ auto source_dest = static_cast<gchar **> (g_malloc_n(3, sizeof(gchar *)));
+ source_dest[0] = g_strdup(this->index_file);
+ source_dest[1] = g_build_filename(tmpl,
+ this->get_name (),
+ "IndexFile",
+ NULL);
+ source_dest[2] = NULL;
+ /* Check if the remote file can be found */
+ if (get_file(&curl, source_dest[0], NULL))
+ {
+ g_strfreev(source_dest);
+ }
+ else
+ {
+ file_list = g_slist_append(file_list, source_dest);
+ }
+ g_object_unref(repo_tmp_dir);
+ g_object_unref(tmp_dir);
+
+ if (curl)
+ {
+ curl_easy_cleanup(curl);
+ }
+ return file_list;
+}
+
+/**
+ * slack::Dl::generate_cache:
+ * @job: A #PkBackendJob.
+ * @tmpl: temporary directory for downloading the files.
+ *
+ * Download files needed to get the information like the list of packages
+ * in available repositories, updates, package descriptions and so on.
+ *
+ * Returns: List of files needed for building the cache.
+ **/
+void
+Dl::generate_cache(PkBackendJob *job, const gchar *tmpl) noexcept
+{
+ gchar **line_tokens, **pkg_tokens, *line, *collection_name = NULL, *list_filename;
+ gboolean skip = FALSE;
+ GFile *list_file;
+ GFileInputStream *fin;
+ GDataInputStream *data_in = NULL;
+ sqlite3_stmt *stmt = NULL;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ /* Check if the temporary directory for this repository exists. If so the file metadata have to be generated */
+ list_filename = g_build_filename(tmpl,
+ this->get_name (),
+ "IndexFile",
+ NULL);
+ list_file = g_file_new_for_path(list_filename);
+ if (!(fin = g_file_read(list_file, NULL, NULL)))
+ {
+ goto out;
+ }
+ data_in = g_data_input_stream_new(G_INPUT_STREAM(fin));
+
+ /* Remove the old entries from this repository */
+ if (sqlite3_prepare_v2(job_data->db,
+ "DELETE FROM repos WHERE repo LIKE @repo",
+ -1,
+ &stmt,
+ NULL) == SQLITE_OK) {
+ sqlite3_bind_text(stmt, 1, this->get_name (), -1, SQLITE_TRANSIENT);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ }
+ if (sqlite3_prepare_v2(job_data->db,
+ "INSERT INTO repos (repo_order, repo) VALUES (@repo_order, @repo)",
+ -1,
+ &stmt,
+ NULL) != SQLITE_OK)
+ {
+ goto out;
+ }
+ sqlite3_bind_int(stmt, 1, this->get_order ());
+ sqlite3_bind_text(stmt, 2, this->get_name (), -1, SQLITE_TRANSIENT);
+ sqlite3_step(stmt);
+ if (sqlite3_finalize(stmt) != SQLITE_OK)
+ {
+ goto out;
+ }
+
+ /* Insert new records */
+ if ((sqlite3_prepare_v2(job_data->db,
+ "INSERT INTO pkglist (full_name, name, ver, arch, "
+ "summary, desc, compressed, uncompressed, cat, repo_order, ext) "
+ "VALUES (@full_name, @name, @ver, @arch, @summary, "
+ "@desc, @compressed, @uncompressed, @cat, @repo_order, @ext)",
+ -1,
+ &stmt,
+ NULL) != SQLITE_OK))
+ {
+ goto out;
+ }
+ sqlite3_exec(job_data->db, "BEGIN TRANSACTION", NULL, NULL, NULL);
+
+ while ((line = g_data_input_stream_read_line(data_in, NULL, NULL, NULL)))
+ {
+ line_tokens = g_strsplit(line, ":", 0);
+ if ((g_strv_length(line_tokens) > 6)
+ && !this->is_blacklisted (line_tokens[0]))
+ {
+ pkg_tokens = split_package_name(line_tokens[0]);
+
+ /* If the split_package_name doesn't return a full name and an
+ * extension, it is a collection. We save its name in this case */
+ if (pkg_tokens[3])
+ {
+ sqlite3_bind_text(stmt, 1, pkg_tokens[3], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 9, "desktop-gnome", -1, SQLITE_STATIC);
+ if (g_strcmp0(line_tokens[1], "obsolete"))
+ {
+ sqlite3_bind_text(stmt, 11, pkg_tokens[4], -1, SQLITE_TRANSIENT);
+ }
+ else
+ {
+ sqlite3_bind_text(stmt, 11, "obsolete", -1, SQLITE_STATIC);
+ }
+ }
+ else if (!collection_name)
+ {
+ collection_name = g_strdup(pkg_tokens[0]);
+ sqlite3_bind_text(stmt, 1, line_tokens[0], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 9, "collections", -1, SQLITE_STATIC);
+ sqlite3_bind_null(stmt, 11);
+ }
+ else
+ {
+ skip = TRUE; /* Skip other candidates for collections */
+ }
+ if (skip)
+ {
+ skip = FALSE;
+ }
+ else
+ {
+ sqlite3_bind_text(stmt, 2, pkg_tokens[0], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 3, pkg_tokens[1], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 4, pkg_tokens[2], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 5, line_tokens[2], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 6, line_tokens[2], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int(stmt, 7, atoi(line_tokens[5]));
+ sqlite3_bind_int(stmt, 8, atoi(line_tokens[5]));
+ sqlite3_bind_int(stmt, 10, this->get_order ());
+
+ sqlite3_step(stmt);
+ sqlite3_clear_bindings(stmt);
+ sqlite3_reset(stmt);
+ }
+ g_strfreev(pkg_tokens);
+ }
+ g_strfreev(line_tokens);
+ g_free(line);
+ }
+
+ /* Create a collection entry */
+ if (collection_name && g_seekable_seek(G_SEEKABLE(data_in), 0, G_SEEK_SET, NULL, NULL)
+ && (sqlite3_prepare_v2(job_data->db,
+ "INSERT INTO collections (name, repo_order, collection_pkg) "
+ "VALUES (@name, @repo_order, @collection_pkg)",
+ -1,
+ &stmt,
+ NULL) == SQLITE_OK))
+ {
+ while ((line = g_data_input_stream_read_line(data_in, NULL, NULL, NULL)))
+ {
+ line_tokens = g_strsplit(line, ":", 0);
+ if ((g_strv_length(line_tokens) > 6)
+ && !this->is_blacklisted (line_tokens[0]))
+ {
+ pkg_tokens = split_package_name(line_tokens[0]);
+
+ /* If not a collection itself */
+ if (pkg_tokens[3]) /* Save this package as a part of the collection */
+ {
+ sqlite3_bind_text(stmt, 1, collection_name, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int(stmt, 2, this->get_order ());
+ sqlite3_bind_text(stmt, 3, pkg_tokens[0], -1, SQLITE_TRANSIENT);
+ sqlite3_step(stmt);
+ sqlite3_clear_bindings(stmt);
+ sqlite3_reset(stmt);
+ }
+ g_strfreev(pkg_tokens);
+ }
+ g_strfreev(line_tokens);
+ g_free(line);
+ }
+ sqlite3_finalize(stmt);
+ }
+ g_free(collection_name);
+
+ sqlite3_exec(job_data->db, "END TRANSACTION", NULL, NULL, NULL);
+
+out:
+ if (data_in)
+ {
+ g_object_unref(data_in);
+ }
+ if (fin)
+ {
+ g_object_unref(fin);
+ }
+ g_object_unref(list_file);
+ g_free(list_filename);
+}
+
+Dl::~Dl () noexcept
+{
+ if (this->blacklist)
+ {
+ g_regex_unref (this->blacklist);
+ }
+
+ g_free (this->name);
+ g_free (this->mirror);
+ g_free (this->index_file);
+}
+
+/**
+ * slack::Dl::Dl:
+ * @name: Repository name.
+ * @mirror: Repository mirror.
+ * @order: Repository order.
+ * @blacklist: Repository blacklist.
+ * @index_file: The index file URL.
+ *
+ * Constructor.
+ *
+ * Return value: New #slack::Dl.
+ **/
+Dl::Dl (const gchar *name, const gchar *mirror,
+ guint8 order, const gchar *blacklist, gchar *index_file) noexcept
+{
+ GRegex *regex;
+
+ if (blacklist)
+ {
+ regex = static_cast<GRegex *> (g_regex_new (blacklist,
+ G_REGEX_OPTIMIZE, static_cast<GRegexMatchFlags> (0), NULL));
+ }
+ else
+ {
+ regex = NULL;
+ }
+
+ this->name = g_strdup (name);
+ this->mirror = g_strdup (mirror);
+
+ this->order = order;
+
+ this->blacklist = regex;
+
+ this->index_file = index_file;
+}
+
+}
diff --git a/backend/dl.h b/backend/dl.h
new file mode 100644
index 0000000..600088d
--- /dev/null
+++ b/backend/dl.h
@@ -0,0 +1,24 @@
+#ifndef __SLACK_DL_H
+#define __SLACK_DL_H
+
+#include "pkgtools.h"
+
+namespace slack {
+
+class Dl final : public Pkgtools
+{
+public:
+ Dl (const gchar *name, const gchar *mirror,
+ guint8 order, const gchar *blacklist, gchar *index_file) noexcept;
+ ~Dl () noexcept;
+
+ GSList *collect_cache_info (const gchar *tmpl) noexcept;
+ void generate_cache (PkBackendJob *job, const gchar *tmpl) noexcept;
+
+private:
+ gchar *index_file;
+};
+
+}
+
+#endif /* __SLACK_DL_H */
diff --git a/backend/job.cc b/backend/job.cc
new file mode 100644
index 0000000..86468ea
--- /dev/null
+++ b/backend/job.cc
@@ -0,0 +1,104 @@
+#include "job.h"
+
+#include <string>
+#include "utils.h"
+
+namespace slack {
+
+/**
+ * Returns true if the package isn't filtered out by the filters, false
+ * otherwise.
+ */
+bool
+filter_package (PkBitfield filters, bool is_installed)
+{
+ if ((is_installed && !pk_bitfield_contain (filters, PK_FILTER_ENUM_NOT_INSTALLED))
+ || (!is_installed && !pk_bitfield_contain (filters, PK_FILTER_ENUM_INSTALLED)))
+ {
+ return true;
+ }
+ return false;
+}
+
+static std::string
+generate_query(PkBitfield filters)
+{
+ std::string query(
+ "SELECT (p1.name || ';' || p1.ver || ';' || p1.arch || ';' || r.repo), p1.summary, "
+ "p1.full_name FROM pkglist AS p1 NATURAL JOIN repos AS r "
+ "WHERE p1.%s LIKE '%%%q%%' AND p1.ext NOT LIKE 'obsolete' AND p1.repo_order = "
+ "(SELECT MIN(p2.repo_order) FROM pkglist AS p2 WHERE p2.name = p1.name GROUP BY p2.name)");
+
+ if (pk_bitfield_contain (filters, PK_FILTER_ENUM_APPLICATION))
+ {
+ query.append(
+ " AND EXISTS (SELECT filelist.full_name "
+ "FROM filelist "
+ "WHERE filelist.full_name = p1.full_name "
+ "AND filelist.filename LIKE 'usr/share/applications/%%.desktop')");
+ }
+ else if (pk_bitfield_contain (filters, PK_FILTER_ENUM_NOT_APPLICATION))
+ {
+ query.append(
+ " AND NOT EXISTS (SELECT filelist.full_name "
+ "FROM filelist "
+ "WHERE filelist.full_name = p1.full_name "
+ "AND filelist.filename LIKE 'usr/share/applications/%%.desktop')");
+ }
+ return query;
+}
+
+}
+
+void
+pk_backend_search_thread (PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ auto job_data = reinterpret_cast<slack::JobData *> (pk_backend_job_get_user_data (job));
+
+ pk_backend_job_set_status (job, PK_STATUS_ENUM_QUERY);
+ pk_backend_job_set_percentage (job, 0);
+
+ gchar **vals;
+ PkBitfield filters;
+ g_variant_get (params, "(t^a&s)", &filters, &vals);
+ gchar *search = g_strjoinv ("%", vals);
+
+ gchar *query = sqlite3_mprintf (slack::generate_query(filters).c_str(),
+ user_data, search);
+
+ sqlite3_stmt *stmt;
+ if ((sqlite3_prepare_v2 (job_data->db, query, -1, &stmt, NULL) == SQLITE_OK))
+ {
+ /* Now we're ready to output all packages */
+ while (sqlite3_step (stmt) == SQLITE_ROW)
+ {
+ PkInfoEnum info = slack::is_installed (
+ reinterpret_cast<const gchar *> (sqlite3_column_text (stmt, 2)));
+
+ if ((info == PK_INFO_ENUM_INSTALLED || info == PK_INFO_ENUM_UPDATING)
+ && slack::filter_package (filters, true))
+ {
+ pk_backend_job_package (job, PK_INFO_ENUM_INSTALLED,
+ reinterpret_cast<const gchar *> (sqlite3_column_text (stmt, 0)),
+ reinterpret_cast<const gchar *> (sqlite3_column_text (stmt, 1)));
+ }
+ else if (info == PK_INFO_ENUM_INSTALLING && slack::filter_package (filters, false))
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_AVAILABLE,
+ reinterpret_cast<const gchar *> (sqlite3_column_text (stmt, 0)),
+ reinterpret_cast<const gchar *> (sqlite3_column_text (stmt, 1)));
+ }
+ }
+ sqlite3_finalize (stmt);
+ }
+ else
+ {
+ pk_backend_job_error_code (job, PK_ERROR_ENUM_CANNOT_GET_FILELIST,
+ "%s", sqlite3_errmsg (job_data->db));
+ }
+
+ sqlite3_free (query);
+ g_free (search);
+
+ pk_backend_job_set_percentage (job, 100);
+}
diff --git a/backend/job.h b/backend/job.h
new file mode 100644
index 0000000..a1684c6
--- /dev/null
+++ b/backend/job.h
@@ -0,0 +1,19 @@
+#ifndef __SLACK_JOB_H
+#define __SLACK_JOB_H
+
+#include <pk-backend.h>
+#include <sqlite3.h>
+
+namespace slack {
+
+bool filter_package (PkBitfield filters, bool is_installed);
+
+}
+
+extern "C" {
+
+void pk_backend_search_thread (PkBackendJob *job, GVariant *params, gpointer user_data);
+
+}
+
+#endif /* __SLACK_JOB_H */
diff --git a/backend/meson.build b/backend/meson.build
new file mode 100644
index 0000000..e01398b
--- /dev/null
+++ b/backend/meson.build
@@ -0,0 +1,53 @@
+add_languages('cpp', native: false)
+
+curl_dep = meson.get_compiler('c').find_library('curl')
+bzip2_dep = dependency('bzip2')
+
+packagekit_backend_slack_module = shared_module(
+ 'pk_backend_slack',
+ 'pk-backend-slack.cc',
+ 'utils.cc',
+ 'pkgtools.cc',
+ 'slackpkg.cc',
+ 'dl.cc',
+ 'job.cc',
+ include_directories: packagekit_src_include,
+ dependencies: [
+ packagekit_glib2_dep,
+ curl_dep,
+ gmodule_dep,
+ sqlite3_dep,
+ bzip2_dep,
+ ],
+ cpp_args: [
+ '-DG_LOG_DOMAIN="PackageKit-Slackware"',
+ '-DLOCALSTATEDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('localstatedir'))),
+ '-DLIBDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('libdir'))),
+ '-DSYSCONFDIR="@0@"'.format(get_option('sysconfdir')),
+ ],
+ override_options: ['c_std=c14', 'cpp_std=c++14'],
+ install: true,
+ install_dir: pk_plugin_dir,
+)
+
+subdir('tests')
+
+slackware_config_data = configuration_data()
+if (target_machine.cpu_family() == 'x86_64')
+ slackware_config_data.set('pkgmain', 'slackware64')
+else
+ slackware_config_data.set('pkgmain', 'slackware')
+endif
+
+configure_file(
+ input: 'Slackware.conf.in',
+ output: 'Slackware.conf',
+ configuration: slackware_config_data,
+ install: true,
+ install_dir: join_paths(get_option('sysconfdir'), 'PackageKit'),
+)
+
+install_data(
+ 'metadata.db',
+ install_dir: join_paths(get_option('localstatedir'), 'cache', 'PackageKit', 'metadata'),
+)
diff --git a/backend/metadata.db b/backend/metadata.db
new file mode 100644
index 0000000..3bb9852
--- /dev/null
+++ b/backend/metadata.db
Binary files differ
diff --git a/backend/pk-backend-slack.cc b/backend/pk-backend-slack.cc
new file mode 100644
index 0000000..7e04866
--- /dev/null
+++ b/backend/pk-backend-slack.cc
@@ -0,0 +1,1078 @@
+#include <dirent.h>
+#include <glib/gstdio.h>
+#include <packagekit-glib2/pk-debug.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <zlib.h>
+#include <curl/curl.h>
+#include <pk-backend.h>
+#include <sqlite3.h>
+#include "job.h"
+#include "dl.h"
+#include "pkgtools.h"
+#include "slackpkg.h"
+#include "utils.h"
+
+using namespace slack;
+
+static GSList *repos = NULL;
+
+void pk_backend_initialize(GKeyFile *conf, PkBackend *backend)
+{
+ gchar *path, **groups;
+ gint ret;
+ gushort i;
+ gsize groups_len;
+ GFile *conf_file;
+ GFileInfo *file_info;
+ GKeyFile *key_conf;
+ GError *err = NULL;
+ gpointer repo = NULL;
+ sqlite3 *db;
+ sqlite3_stmt *stmt;
+
+ g_debug("backend: initialize");
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+
+ /* Open the database. We will need it to save the time the configuration file was last modified. */
+ path = g_build_filename(LOCALSTATEDIR, "cache", "PackageKit", "metadata", "metadata.db", NULL);
+ if (sqlite3_open(path, &db) != SQLITE_OK)
+ {
+ g_error("%s: %s", path, sqlite3_errmsg(db));
+ }
+ g_free(path);
+
+ /* Read the configuration file */
+ key_conf = g_key_file_new();
+ path = g_build_filename(SYSCONFDIR, "PackageKit", "Slackware.conf", NULL);
+ g_key_file_load_from_file(key_conf, path, G_KEY_FILE_NONE, &err);
+ if (err)
+ {
+ g_error("%s: %s", path, err->message);
+ g_error_free(err);
+ }
+
+ conf_file = g_file_new_for_path(path);
+ if (!(file_info = g_file_query_info(conf_file,
+ "time::modified-usec",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL,
+ &err)))
+ {
+ g_error("%s", err->message);
+ g_error_free(err);
+ }
+
+ if ((ret = sqlite3_prepare_v2(db,
+ "UPDATE cache_info SET value = ? WHERE key LIKE 'last_modification'",
+ -1,
+ &stmt,
+ NULL)) == SQLITE_OK) {
+ ret = sqlite3_bind_int(stmt, 1, g_file_info_get_attribute_uint32(file_info, "time::modified-usec"));
+ if (ret == SQLITE_OK)
+ {
+ ret = sqlite3_step(stmt);
+ }
+ sqlite3_finalize(stmt);
+ }
+ if ((ret != SQLITE_OK) && (ret != SQLITE_DONE))
+ {
+ g_error("%s: %s", path, sqlite3_errstr(ret));
+ }
+ else if (!sqlite3_changes(db))
+ {
+ g_error("Failed to update database: %s", path);
+ }
+
+ g_object_unref(file_info);
+ g_object_unref(conf_file);
+ sqlite3_close_v2(db);
+ g_free(path);
+
+ /* Initialize an object for each well-formed repository */
+ groups = g_key_file_get_groups(key_conf, &groups_len);
+ for (i = 0; i < groups_len; i++)
+ {
+ gchar *blacklist = g_key_file_get_string(key_conf, groups[i], "Blacklist", NULL);
+ gchar *mirror = g_key_file_get_string(key_conf, groups[i], "Mirror", NULL);
+
+ if (g_key_file_has_key(key_conf, groups[i], "Priority", NULL))
+ {
+ repo = new Slackpkg (groups[i], mirror, i + 1, blacklist,
+ g_key_file_get_string_list(key_conf, groups[i], "Priority", NULL, NULL));
+ }
+ else if (g_key_file_has_key(key_conf, groups[i], "IndexFile", NULL))
+ {
+ repo = new Dl (groups[i], mirror, i + 1, blacklist,
+ g_key_file_get_string(key_conf, groups[i], "IndexFile", NULL));
+ }
+
+ if (repo)
+ {
+ repos = g_slist_append(repos, repo);
+ }
+ else
+ {
+ g_free(groups[i]);
+ }
+ g_free(mirror);
+ g_free(blacklist);
+ }
+ g_free(groups);
+
+ g_key_file_free(key_conf);
+}
+
+void
+pk_backend_destroy(PkBackend *backend)
+{
+ g_debug("backend: destroy");
+
+ for (GSList *l = repos; l; l = g_slist_next (l))
+ {
+ delete static_cast<Pkgtools *> (l->data);
+ }
+
+ g_slist_free (repos);
+ curl_global_cleanup ();
+}
+
+gchar **
+pk_backend_get_mime_types(PkBackend *backend)
+{
+ const gchar *mime_types[] = {
+ "application/x-xz-compressed-tar",
+ "application/x-compressed-tar",
+ "application/x-bzip-compressed-tar",
+ "application/x-lzma-compressed-tar",
+ NULL
+ };
+
+ return g_strdupv((gchar **) mime_types);
+}
+
+gboolean
+pk_backend_supports_parallelization(PkBackend *backend)
+{
+ return FALSE;
+}
+
+const gchar *
+pk_backend_get_description(PkBackend* backend)
+{
+ return "Slackware";
+}
+
+const gchar *
+pk_backend_get_author(PkBackend* backend)
+{
+ return "Eugene Wissner <belka@caraus.de>";
+}
+
+PkBitfield
+pk_backend_get_groups(PkBackend *backend)
+{
+ return pk_bitfield_from_enums(PK_GROUP_ENUM_COLLECTIONS,
+ PK_GROUP_ENUM_SYSTEM,
+ PK_GROUP_ENUM_ADMIN_TOOLS,
+ PK_GROUP_ENUM_PROGRAMMING,
+ PK_GROUP_ENUM_PUBLISHING,
+ PK_GROUP_ENUM_DOCUMENTATION,
+ PK_GROUP_ENUM_DESKTOP_KDE,
+ PK_GROUP_ENUM_LOCALIZATION,
+ PK_GROUP_ENUM_NETWORK,
+ PK_GROUP_ENUM_DESKTOP_OTHER,
+ PK_GROUP_ENUM_ACCESSORIES,
+ PK_GROUP_ENUM_DESKTOP_XFCE,
+ PK_GROUP_ENUM_GAMES,
+ PK_GROUP_ENUM_OTHER,
+ PK_GROUP_ENUM_UNKNOWN,
+ -1);
+}
+
+void
+pk_backend_start_job(PkBackend *backend, PkBackendJob *job)
+{
+ gchar *db_filename = NULL;
+ JobData *job_data = g_new0(JobData, 1);
+
+ pk_backend_job_set_allow_cancel(job, TRUE);
+ pk_backend_job_set_allow_cancel(job, FALSE);
+
+ db_filename = g_build_filename(LOCALSTATEDIR, "cache", "PackageKit", "metadata", "metadata.db", NULL);
+ if (sqlite3_open(db_filename, &job_data->db) == SQLITE_OK) { /* Some SQLite settings */
+ sqlite3_exec(job_data->db, "PRAGMA foreign_keys = ON", NULL, NULL, NULL);
+ }
+ else
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_NO_CACHE,
+ "%s: %s",
+ db_filename,
+ sqlite3_errmsg(job_data->db));
+ goto out;
+ }
+
+ pk_backend_job_set_user_data(job, job_data);
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_RUNNING);
+
+out:
+ g_free(db_filename);
+}
+
+void
+pk_backend_stop_job(PkBackend *backend, PkBackendJob *job)
+{
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ if (job_data->curl)
+ {
+ curl_easy_cleanup(job_data->curl);
+ }
+
+ sqlite3_close(job_data->db);
+ g_free(job_data);
+ pk_backend_job_set_user_data(job, NULL);
+}
+
+void
+pk_backend_search_names(PkBackend *backend, PkBackendJob *job, PkBitfield filters, gchar **values)
+{
+ pk_backend_job_thread_create(job, pk_backend_search_thread, (gpointer) "name", NULL);
+}
+
+void
+pk_backend_search_details(PkBackend *backend, PkBackendJob *job, PkBitfield filters, gchar **values)
+{
+ pk_backend_job_thread_create(job, pk_backend_search_thread, (gpointer) "desc", NULL);
+}
+
+void
+pk_backend_search_groups(PkBackend *backend, PkBackendJob *job, PkBitfield filters, gchar **values)
+{
+ pk_backend_job_thread_create(job, pk_backend_search_thread, (gpointer) "cat", NULL);
+}
+
+static void
+pk_backend_search_files_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar **vals, *search;
+ gchar *query;
+ sqlite3_stmt *stmt;
+ PkInfoEnum ret;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_QUERY);
+ pk_backend_job_set_percentage(job, 0);
+
+ g_variant_get(params, "(t^a&s)", NULL, &vals);
+ search = g_strjoinv("%", vals);
+
+ query = sqlite3_mprintf("SELECT (p.name || ';' || p.ver || ';' || p.arch || ';' || r.repo), p.summary, "
+ "p.full_name FROM filelist AS f NATURAL JOIN pkglist AS p NATURAL JOIN repos AS r "
+ "WHERE f.filename LIKE '%%%q%%' GROUP BY f.full_name", search);
+
+ if ((sqlite3_prepare_v2(job_data->db, query, -1, &stmt, NULL) == SQLITE_OK))
+ {
+ /* Now we're ready to output all packages */
+ while (sqlite3_step(stmt) == SQLITE_ROW)
+ {
+ ret = is_installed((gchar*) sqlite3_column_text(stmt, 2));
+ if ((ret == PK_INFO_ENUM_INSTALLED) || (ret == PK_INFO_ENUM_UPDATING))
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_INSTALLED,
+ (gchar*) sqlite3_column_text(stmt, 0),
+ (gchar*) sqlite3_column_text(stmt, 1));
+ }
+ else if (ret == PK_INFO_ENUM_INSTALLING)
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_AVAILABLE,
+ (gchar*) sqlite3_column_text(stmt, 0),
+ (gchar*) sqlite3_column_text(stmt, 1));
+ }
+ }
+ sqlite3_finalize(stmt);
+ }
+ else
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_CANNOT_GET_FILELIST, "%s", sqlite3_errmsg(job_data->db));
+ }
+ sqlite3_free(query);
+ g_free(search);
+
+ pk_backend_job_set_percentage(job, 100);
+}
+
+void
+pk_backend_search_files(PkBackend *backend, PkBackendJob *job, PkBitfield filters, gchar **values)
+{
+ pk_backend_job_thread_create(job, pk_backend_search_files_thread, NULL, NULL);
+}
+
+static void
+pk_backend_get_details_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar **pkg_ids, *homepage = NULL;
+ gchar** tokens;
+ gsize i;
+ GString *desc;
+ GRegex *expr;
+ GMatchInfo *match_info;
+ GError *err = NULL;
+ sqlite3_stmt *stmt;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_QUERY);
+
+ g_variant_get(params, "(^a&s)", &pkg_ids);
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT p.desc, p.cat, p.uncompressed FROM pkglist AS p NATURAL JOIN repos AS r "
+ "WHERE name LIKE @name AND r.repo LIKE @repo AND ext NOT LIKE 'obsolete'",
+ -1,
+ &stmt,
+ NULL) != SQLITE_OK)) {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_CANNOT_GET_FILELIST, "%s", sqlite3_errmsg(job_data->db));
+ goto out;
+ }
+
+ tokens = pk_package_id_split(pkg_ids[0]);
+ sqlite3_bind_text(stmt, 1, tokens[PK_PACKAGE_ID_NAME], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 2, tokens[PK_PACKAGE_ID_DATA], -1, SQLITE_TRANSIENT);
+ g_strfreev(tokens);
+
+ if (sqlite3_step(stmt) != SQLITE_ROW)
+ goto out;
+
+ desc = g_string_new((gchar *) sqlite3_column_text(stmt, 0));
+
+ /* Regular expression for searching a homepage */
+ expr = g_regex_new("(?:http|ftp):\\/\\/[[:word:]\\/\\-\\.]+[[:word:]\\/](?=\\.?$)",
+ (GRegexCompileFlags)(G_REGEX_OPTIMIZE | G_REGEX_DUPNAMES),
+ (GRegexMatchFlags)(0),
+ &err);
+ if (err)
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_UNKNOWN, "%s", err->message);
+ g_error_free(err);
+ goto out;
+ }
+ if (g_regex_match(expr, desc->str, (GRegexMatchFlags)0, &match_info))
+ {
+ homepage = g_match_info_fetch(match_info, 0); /* URL */
+ /* Remove the last sentence with the copied URL */
+ for (i = desc->len - 1; i > 0; i--)
+ {
+ if ((desc->str[i - 1] == '.') && (desc->str[i] == ' '))
+ {
+ g_string_truncate(desc, i);
+ break;
+ }
+ }
+ g_match_info_free(match_info);
+ }
+ g_regex_unref(expr);
+
+ /* Ready */
+ pk_backend_job_details(job,
+ pkg_ids[0],
+ NULL,
+ NULL,
+ pk_group_enum_from_string((gchar *) sqlite3_column_text(stmt, 1)),
+ desc->str,
+ homepage,
+ sqlite3_column_int(stmt, 2),
+ G_MAXUINT64);
+
+ g_free(homepage);
+ if (desc)
+ {
+ g_string_free(desc, TRUE);
+ }
+
+out:
+ sqlite3_finalize(stmt);
+}
+
+void
+pk_backend_get_details(PkBackend *backend, PkBackendJob *job, gchar **package_ids)
+{
+ pk_backend_job_thread_create(job, pk_backend_get_details_thread, NULL, NULL);
+}
+
+static void
+pk_backend_resolve_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar **vals, **val;
+ sqlite3_stmt *stmt;
+ PkInfoEnum ret;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_QUERY);
+ pk_backend_job_set_percentage(job, 0);
+
+ g_variant_get(params, "(t^a&s)", NULL, &vals);
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT (p1.name || ';' || p1.ver || ';' || p1.arch || ';' || r.repo), p1.summary, "
+ "p1.full_name FROM pkglist AS p1 NATURAL JOIN repos AS r "
+ "WHERE p1.name LIKE @search AND p1.repo_order = "
+ "(SELECT MIN(p2.repo_order) FROM pkglist AS p2 WHERE p2.name = p1.name GROUP BY p2.name)",
+ -1,
+ &stmt,
+ NULL) == SQLITE_OK)) {
+ /* Output packages matching each pattern */
+ for (val = vals; *val; val++)
+ {
+ sqlite3_bind_text(stmt, 1, *val, -1, SQLITE_TRANSIENT);
+
+ while (sqlite3_step(stmt) == SQLITE_ROW)
+ {
+ ret = is_installed((gchar*) sqlite3_column_text(stmt, 2));
+ if ((ret == PK_INFO_ENUM_INSTALLED) || (ret == PK_INFO_ENUM_UPDATING))
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_INSTALLED,
+ (gchar*) sqlite3_column_text(stmt, 0),
+ (gchar*) sqlite3_column_text(stmt, 1));
+ }
+ else if (ret == PK_INFO_ENUM_INSTALLING)
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_AVAILABLE,
+ (gchar*) sqlite3_column_text(stmt, 0),
+ (gchar*) sqlite3_column_text(stmt, 1));
+ }
+ }
+
+ sqlite3_clear_bindings(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ } else {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_CANNOT_GET_FILELIST, "%s", sqlite3_errmsg(job_data->db));
+ }
+
+ pk_backend_job_set_percentage(job, 100);
+}
+
+void
+pk_backend_resolve(PkBackend *backend, PkBackendJob *job, PkBitfield filters, gchar **packages)
+{
+ pk_backend_job_thread_create(job, pk_backend_resolve_thread, NULL, NULL);
+}
+
+static void
+pk_backend_download_packages_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar *dir_path, *path, **pkg_ids, *to_strv[] = {NULL, NULL};
+ guint i;
+ sqlite3_stmt *stmt;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ g_variant_get(params, "(^a&ss)", &pkg_ids, &dir_path);
+ pk_backend_job_set_status (job, PK_STATUS_ENUM_DOWNLOAD);
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT summary, (full_name || '.' || ext) FROM pkglist NATURAL JOIN repos "
+ "WHERE name LIKE @name AND ver LIKE @ver AND arch LIKE @arch AND repo LIKE @repo",
+ -1,
+ &stmt,
+ NULL) != SQLITE_OK))
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_CANNOT_GET_FILELIST, "%s", sqlite3_errmsg(job_data->db));
+ goto out;
+ }
+
+ for (i = 0; pkg_ids[i]; ++i)
+ {
+ gchar **tokens = pk_package_id_split(pkg_ids[i]);
+
+ sqlite3_bind_text(stmt, 1, tokens[PK_PACKAGE_ID_NAME], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 2, tokens[PK_PACKAGE_ID_VERSION], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 3, tokens[PK_PACKAGE_ID_ARCH], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(stmt, 4, tokens[PK_PACKAGE_ID_DATA], -1, SQLITE_TRANSIENT);
+ if (sqlite3_step(stmt) == SQLITE_ROW)
+ {
+ GSList *repo;
+ if ((repo = g_slist_find_custom(repos, tokens[PK_PACKAGE_ID_DATA], cmp_repo)))
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_DOWNLOADING,
+ pkg_ids[i],
+ (gchar *) sqlite3_column_text(stmt, 0));
+ static_cast<Pkgtools *> (repo->data)->download (job,
+ dir_path, tokens[PK_PACKAGE_ID_NAME]);
+ path = g_build_filename(dir_path, (gchar *) sqlite3_column_text(stmt, 1), NULL);
+ to_strv[0] = path;
+ pk_backend_job_files(job, NULL, to_strv);
+ g_free(path);
+ }
+ }
+ sqlite3_clear_bindings(stmt);
+ sqlite3_reset(stmt);
+ g_strfreev(tokens);
+ }
+
+out:
+ sqlite3_finalize(stmt);
+}
+
+void
+pk_backend_download_packages(PkBackend *backend, PkBackendJob *job, gchar **package_ids, const gchar *directory)
+{
+ pk_backend_job_thread_create(job, pk_backend_download_packages_thread, NULL, NULL);
+}
+
+static void
+pk_backend_install_packages_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar *dest_dir_name;
+ gchar **pkg_ids;
+ guint i;
+ gdouble percent_step;
+ GSList *install_list = NULL, *l;
+ sqlite3_stmt *pkglist_stmt = NULL, *collection_stmt = NULL;
+ PkBitfield transaction_flags = 0;
+ PkInfoEnum ret;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ g_variant_get(params, "(t^a&s)", &transaction_flags, &pkg_ids);
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_DEP_RESOLVE);
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT summary, cat FROM pkglist NATURAL JOIN repos "
+ "WHERE name LIKE @name AND ver LIKE @ver AND arch LIKE @arch AND repo LIKE @repo",
+ -1,
+ &pkglist_stmt,
+ NULL) != SQLITE_OK) ||
+ (sqlite3_prepare_v2(job_data->db,
+ "SELECT (c.collection_pkg || ';' || p.ver || ';' || p.arch || ';' || r.repo), p.summary, "
+ "p.full_name, p.ext FROM collections AS c "
+ "JOIN pkglist AS p ON c.collection_pkg = p.name "
+ "JOIN repos AS r ON p.repo_order = r.repo_order "
+ "WHERE c.name LIKE @name AND r.repo LIKE @repo",
+ -1,
+ &collection_stmt,
+ NULL) != SQLITE_OK))
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_CANNOT_GET_FILELIST, "%s", sqlite3_errmsg(job_data->db));
+ goto out;
+ }
+
+ for (i = 0; pkg_ids[i]; i++)
+ {
+ gchar **tokens = pk_package_id_split(pkg_ids[i]);
+ sqlite3_bind_text(pkglist_stmt, 1, tokens[PK_PACKAGE_ID_NAME], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(pkglist_stmt, 2, tokens[PK_PACKAGE_ID_VERSION], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(pkglist_stmt, 3, tokens[PK_PACKAGE_ID_ARCH], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(pkglist_stmt, 4, tokens[PK_PACKAGE_ID_DATA], -1, SQLITE_TRANSIENT);
+
+ if (sqlite3_step(pkglist_stmt) == SQLITE_ROW)
+ {
+ /* If it isn't a collection */
+ if (g_strcmp0((gchar *) sqlite3_column_text(pkglist_stmt, 1), "collections"))
+ {
+ if (pk_bitfield_contain(transaction_flags, PK_TRANSACTION_FLAG_ENUM_SIMULATE))
+ {
+ pk_backend_job_package(job, PK_INFO_ENUM_INSTALLING,
+ pkg_ids[i],
+ (gchar *) sqlite3_column_text(pkglist_stmt, 0));
+ }
+ else
+ {
+ install_list = g_slist_append(install_list, g_strdup(pkg_ids[i]));
+ }
+ }
+ else
+ {
+ sqlite3_bind_text(collection_stmt, 1, tokens[PK_PACKAGE_ID_NAME], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(collection_stmt, 2, tokens[PK_PACKAGE_ID_DATA], -1, SQLITE_TRANSIENT);
+
+ while (sqlite3_step(collection_stmt) == SQLITE_ROW)
+ {
+ ret = is_installed((gchar*) sqlite3_column_text(collection_stmt, 2));
+ if ((ret == PK_INFO_ENUM_INSTALLING) || (ret == PK_INFO_ENUM_UPDATING))
+ {
+ if ((pk_bitfield_contain(transaction_flags, PK_TRANSACTION_FLAG_ENUM_SIMULATE)) &&
+ !g_strcmp0((gchar *) sqlite3_column_text(collection_stmt, 3), "obsolete"))
+ {
+ /* TODO: Don't just skip obsolete packages but remove them */
+ }
+ else if (pk_bitfield_contain(transaction_flags, PK_TRANSACTION_FLAG_ENUM_SIMULATE))
+ {
+ pk_backend_job_package(job, ret,
+ (gchar *) sqlite3_column_text(collection_stmt, 0),
+ (gchar *) sqlite3_column_text(collection_stmt, 1));
+ }
+ else
+ {
+ install_list = g_slist_append(install_list,
+ g_strdup((gchar *) sqlite3_column_text(collection_stmt, 0)));
+ }
+ }
+ }
+ sqlite3_clear_bindings(collection_stmt);
+ sqlite3_reset(collection_stmt);
+ }
+ }
+
+ sqlite3_clear_bindings(pkglist_stmt);
+ sqlite3_reset(pkglist_stmt);
+ g_strfreev(tokens);
+ }
+
+ if (install_list && !pk_bitfield_contain(transaction_flags, PK_TRANSACTION_FLAG_ENUM_SIMULATE))
+ {
+ /* / 2 means total percentage for installing and for downloading */
+ percent_step = 100.0 / g_slist_length(install_list) / 2;
+
+ /* Download the packages */
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_DOWNLOAD);
+ dest_dir_name = g_build_filename(LOCALSTATEDIR, "cache", "PackageKit", "downloads", NULL);
+ for (l = install_list, i = 0; l; l = g_slist_next(l), i++)
+ {
+ gchar **tokens;
+ GSList *repo;
+
+ pk_backend_job_set_percentage(job, percent_step * i);
+ tokens = pk_package_id_split((gchar *)(l->data));
+ repo = g_slist_find_custom(repos, tokens[PK_PACKAGE_ID_DATA], cmp_repo);
+
+ if (repo)
+ {
+ static_cast<Pkgtools *> (repo->data)->download (job,
+ dest_dir_name, tokens[PK_PACKAGE_ID_NAME]);
+ }
+ g_strfreev(tokens);
+ }
+ g_free(dest_dir_name);
+
+ /* Install the packages */
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_INSTALL);
+ for (l = install_list; l; l = g_slist_next(l), i++)
+ {
+ gchar **tokens;
+ GSList *repo;
+
+ pk_backend_job_set_percentage(job, percent_step * i);
+ tokens = pk_package_id_split((gchar *)(l->data));
+ repo = g_slist_find_custom(repos, tokens[PK_PACKAGE_ID_DATA], cmp_repo);
+
+ if (repo)
+ {
+ static_cast<Pkgtools *> (repo->data)->install (job, tokens[PK_PACKAGE_ID_NAME]);
+ }
+ g_strfreev(tokens);
+ }
+ }
+ g_slist_free_full(install_list, g_free);
+
+out:
+ sqlite3_finalize(pkglist_stmt);
+ sqlite3_finalize(collection_stmt);
+}
+
+void
+pk_backend_install_packages(PkBackend *backend,
+ PkBackendJob *job,
+ PkBitfield transaction_flags,
+ gchar **package_ids)
+{
+ pk_backend_job_thread_create(job, pk_backend_install_packages_thread, NULL, NULL);
+}
+
+static void
+pk_backend_remove_packages_thread(PkBackendJob* job, GVariant* params, gpointer user_data)
+{
+ gchar **pkg_ids, *cmd_line;
+ guint i;
+ gdouble percent_step;
+ gboolean allow_deps, autoremove;
+ GError *err = NULL;
+ PkBitfield transaction_flags = 0;
+
+ g_variant_get(params, "(t^a&sbb)", &transaction_flags, &pkg_ids, &allow_deps, &autoremove);
+
+ if (pk_bitfield_contain(transaction_flags, PK_TRANSACTION_FLAG_ENUM_SIMULATE))
+ {
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_DEP_RESOLVE);
+ }
+ else
+ {
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_REMOVE);
+
+ /* Add percent_step percents per removed package */
+ percent_step = 100.0 / g_strv_length(pkg_ids);
+ for (i = 0; pkg_ids[i]; i++)
+ {
+ gchar **tokens;
+
+ pk_backend_job_set_percentage(job, percent_step * i);
+ tokens = pk_package_id_split(pkg_ids[i]);
+ cmd_line = g_strconcat("/sbin/removepkg ", tokens[PK_PACKAGE_ID_NAME], NULL);
+
+ /* Pkgtools return always 0 */
+ g_spawn_command_line_sync(cmd_line, NULL, NULL, NULL, &err);
+
+ g_free(cmd_line);
+ g_strfreev(tokens);
+
+ if (err)
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_PACKAGE_FAILED_TO_REMOVE, "%s", err->message);
+ g_error_free(err);
+
+ return;
+ }
+
+ pk_backend_job_set_percentage(job, 100);
+ }
+ }
+}
+
+void
+pk_backend_remove_packages(PkBackend *backend,
+ PkBackendJob *job,
+ PkBitfield transaction_flags,
+ gchar **package_ids,
+ gboolean allow_deps,
+ gboolean autoremove)
+{
+ pk_backend_job_thread_create(job, pk_backend_remove_packages_thread, NULL, NULL);
+}
+
+static void
+pk_backend_get_updates_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar *pkg_id, *full_name, *desc;
+ const gchar *pkg_metadata_filename;
+ GFile *pkg_metadata_dir;
+ GFileEnumerator *pkg_metadata_enumerator;
+ GFileInfo *pkg_metadata_file_info;
+ GError *err = NULL;
+ sqlite3_stmt *stmt;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_QUERY);
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT p1.full_name, p1.name, p1.ver, p1.arch, r.repo, p1.summary, p1.ext "
+ "FROM pkglist AS p1 NATURAL JOIN repos AS r "
+ "WHERE p1.name LIKE @name AND p1.repo_order = "
+ "(SELECT MIN(p2.repo_order) FROM pkglist AS p2 WHERE p2.name = p1.name GROUP BY p2.name)",
+ -1,
+ &stmt,
+ NULL) != SQLITE_OK))
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_CANNOT_GET_FILELIST, "%s", sqlite3_errmsg(job_data->db));
+ goto out;
+ }
+
+ /* Read the package metadata directory and comprare all installed packages with ones in the cache */
+ pkg_metadata_dir = g_file_new_for_path("/var/log/packages");
+ pkg_metadata_enumerator = g_file_enumerate_children(pkg_metadata_dir, "standard::name",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &err);
+ g_object_unref(pkg_metadata_dir);
+ if (err)
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_NO_CACHE, "/var/log/packages: %s", err->message);
+ g_error_free(err);
+ goto out;
+ }
+
+ while ((pkg_metadata_file_info = g_file_enumerator_next_file(pkg_metadata_enumerator, NULL, NULL)))
+ {
+ gchar **tokens;
+
+ pkg_metadata_filename = g_file_info_get_name(pkg_metadata_file_info);
+ tokens = split_package_name(pkg_metadata_filename);
+
+ /* Select the package from the database */
+ sqlite3_bind_text(stmt, 1, tokens[0], -1, SQLITE_TRANSIENT);
+
+ /* If there are more packages with the same name, remember the one from the
+ * repository with the lowest order. */
+ if ((sqlite3_step(stmt) == SQLITE_ROW)
+ || g_slist_find_custom(repos, ((gchar *) sqlite3_column_text(stmt, 4)), cmp_repo))
+ {
+
+ full_name = g_strdup((gchar *) sqlite3_column_text(stmt, 0));
+
+ if (!g_strcmp0((gchar *) sqlite3_column_text(stmt, 6), "obsolete"))
+ { /* Remove if obsolete */
+ pkg_id = pk_package_id_build(tokens[PK_PACKAGE_ID_NAME],
+ tokens[PK_PACKAGE_ID_VERSION],
+ tokens[PK_PACKAGE_ID_ARCH],
+ "obsolete");
+ /* TODO:
+ * 1: Use the repository name instead of "obsolete" above and check in pk_backend_update_packages()
+ if the package is obsolete or not
+ * 2: Get description from /var/log/packages, not from the database */
+ desc = g_strdup((gchar *) sqlite3_column_text(stmt, 5));
+
+ pk_backend_job_package(job, PK_INFO_ENUM_REMOVING, pkg_id, desc);
+
+ g_free(desc);
+ g_free(pkg_id);
+ }
+ else if (g_strcmp0(pkg_metadata_filename, full_name))
+ { /* Update available */
+ pkg_id = pk_package_id_build((gchar *) sqlite3_column_text(stmt, 1),
+ (gchar *) sqlite3_column_text(stmt, 2),
+ (gchar *) sqlite3_column_text(stmt, 3),
+ (gchar *) sqlite3_column_text(stmt, 4));
+ desc = g_strdup((gchar *) sqlite3_column_text(stmt, 5));
+
+ pk_backend_job_package(job, PK_INFO_ENUM_NORMAL, pkg_id, desc);
+
+ g_free(desc);
+ g_free(pkg_id);
+ }
+ g_free(full_name);
+ }
+
+ sqlite3_clear_bindings(stmt);
+ sqlite3_reset(stmt);
+
+ g_strfreev(tokens);
+ g_object_unref(pkg_metadata_file_info);
+ }
+ g_object_unref(pkg_metadata_enumerator);
+
+out:
+ sqlite3_finalize(stmt);
+}
+
+void
+pk_backend_get_updates(PkBackend *backend, PkBackendJob *job, PkBitfield filters)
+{
+ pk_backend_job_thread_create(job, pk_backend_get_updates_thread, NULL, NULL);
+}
+
+static void
+pk_backend_update_packages_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar *dest_dir_name, *cmd_line, **pkg_ids;
+ guint i;
+ PkBitfield transaction_flags = 0;
+
+ g_variant_get(params, "(t^a&s)", &transaction_flags, &pkg_ids);
+
+ if (!pk_bitfield_contain(transaction_flags, PK_TRANSACTION_FLAG_ENUM_SIMULATE)) {
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_DOWNLOAD);
+
+ /* Download the packages */
+ dest_dir_name = g_build_filename(LOCALSTATEDIR, "cache", "PackageKit", "downloads", NULL);
+ for (i = 0; pkg_ids[i]; i++)
+ {
+ gchar **tokens = pk_package_id_split(pkg_ids[i]);
+
+ if (g_strcmp0(tokens[PK_PACKAGE_ID_DATA], "obsolete"))
+ {
+ GSList *repo = g_slist_find_custom(repos, tokens[PK_PACKAGE_ID_DATA], cmp_repo);
+
+ if (repo)
+ {
+ static_cast<Pkgtools *> (repo->data)->download (job,
+ dest_dir_name, tokens[PK_PACKAGE_ID_NAME]);
+ }
+ }
+
+ g_strfreev(tokens);
+ }
+ g_free(dest_dir_name);
+
+ /* Install the packages */
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_UPDATE);
+ for (i = 0; pkg_ids[i]; i++)
+ {
+ gchar **tokens = pk_package_id_split(pkg_ids[i]);
+
+ if (g_strcmp0(tokens[PK_PACKAGE_ID_DATA], "obsolete"))
+ {
+ GSList *repo = g_slist_find_custom(repos, tokens[PK_PACKAGE_ID_DATA], cmp_repo);
+
+ if (repo)
+ {
+ static_cast<Pkgtools *> (repo->data)->install (job,
+ tokens[PK_PACKAGE_ID_NAME]);
+ }
+ }
+ else
+ {
+ /* Remove obsolete package
+ * TODO: Removing should be an independent operation (not during installing updates) */
+ cmd_line = g_strconcat("/sbin/removepkg ", tokens[PK_PACKAGE_ID_NAME], NULL);
+ g_spawn_command_line_sync(cmd_line, NULL, NULL, NULL, NULL);
+ g_free(cmd_line);
+ }
+ g_strfreev(tokens);
+ }
+ }
+}
+
+void
+pk_backend_update_packages(PkBackend *backend,
+ PkBackendJob *job,
+ PkBitfield transaction_flags,
+ gchar **package_ids)
+{
+ pk_backend_job_thread_create(job, pk_backend_update_packages_thread, NULL, NULL);
+}
+
+static void
+pk_backend_refresh_cache_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ gchar *tmp_dir_name, *db_err, *path = NULL;
+ gint ret;
+ gboolean force;
+ GSList *file_list = NULL;
+ GFile *db_file = NULL;
+ GFileInfo *file_info = NULL;
+ GError *err = NULL;
+ sqlite3_stmt *stmt = NULL;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_DOWNLOAD_CHANGELOG);
+
+ /* Create temporary directory */
+ tmp_dir_name = g_dir_make_tmp("PackageKit.XXXXXX", &err);
+ if (!tmp_dir_name)
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_INTERNAL_ERROR, "%s", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ g_variant_get(params, "(b)", &force);
+
+ /* Force the complete cache refresh if the read configuration file is newer than the metadata cache */
+ if (!force)
+ {
+ path = g_build_filename(LOCALSTATEDIR, "cache", "PackageKit", "metadata", "metadata.db", NULL);
+ db_file = g_file_new_for_path(path);
+ file_info = g_file_query_info(db_file, "time::modified-usec", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &err);
+ if (err)
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_NO_CACHE, "%s: %s", path, err->message);
+ g_error_free(err);
+ goto out;
+ }
+ ret = sqlite3_prepare_v2(job_data->db,
+ "SELECT value FROM cache_info WHERE key LIKE 'last_modification'",
+ -1,
+ &stmt,
+ NULL);
+ if ((ret != SQLITE_OK) || ((ret = sqlite3_step(stmt)) != SQLITE_ROW))
+ {
+ pk_backend_job_error_code(job,
+ PK_ERROR_ENUM_NO_CACHE,
+ "%s: %s",
+ path,
+ sqlite3_errstr(ret));
+ goto out;
+ }
+ if ((guint32) sqlite3_column_int(stmt, 0) > g_file_info_get_attribute_uint32(file_info, "time::modified-usec"))
+ {
+ force = TRUE;
+ }
+ }
+ if (force) /* It should empty all tables */
+ {
+ if (sqlite3_exec(job_data->db, "DELETE FROM repos", NULL, 0, &db_err) != SQLITE_OK)
+ {
+ pk_backend_job_error_code(job, PK_ERROR_ENUM_INTERNAL_ERROR, "%s", db_err);
+ sqlite3_free(db_err);
+ goto out;
+ }
+ }
+
+ // Get list of files that should be downloaded.
+ for (GSList *l = repos; l; l = g_slist_next(l))
+ {
+ file_list = g_slist_concat(file_list,
+ static_cast<Pkgtools *> (l->data)->collect_cache_info (tmp_dir_name));
+ }
+
+ /* Download repository */
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_DOWNLOAD_REPOSITORY);
+
+ for (GSList *l = file_list; l; l = g_slist_next(l))
+ {
+ get_file(&job_data->curl, static_cast<gchar **> (l->data)[0],
+ static_cast<gchar **> (l->data)[1]);
+ }
+ g_slist_free_full(file_list, (GDestroyNotify)g_strfreev);
+
+ /* Refresh cache */
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_REFRESH_CACHE);
+
+ for (GSList *l = repos; l; l = g_slist_next(l))
+ {
+ static_cast<Pkgtools *> (l->data)->generate_cache (job, tmp_dir_name);
+ }
+
+out:
+ sqlite3_finalize(stmt);
+ if (file_info)
+ {
+ g_object_unref(file_info);
+ }
+ if (db_file)
+ {
+ g_object_unref(db_file);
+ }
+ g_free(path);
+
+ pk_directory_remove_contents(tmp_dir_name);
+ g_rmdir(tmp_dir_name);
+ g_free(tmp_dir_name);
+}
+
+void
+pk_backend_refresh_cache(PkBackend *backend, PkBackendJob *job, gboolean force)
+{
+ pk_backend_job_thread_create(job, pk_backend_refresh_cache_thread, NULL, NULL);
+}
+
+static void
+pk_backend_get_update_detail_thread(PkBackendJob *job, GVariant *params, gpointer user_data)
+{
+ guint i;
+ gchar **pkg_ids;
+
+ pk_backend_job_set_status(job, PK_STATUS_ENUM_QUERY);
+
+ g_variant_get(params, "(^a&s)", &pkg_ids);
+
+ for (i = 0; pkg_ids[i] != NULL; i++)
+ {
+ pk_backend_job_update_detail (job,
+ pkg_ids[i],
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ PK_RESTART_ENUM_NONE,
+ NULL,
+ NULL,
+ PK_UPDATE_STATE_ENUM_STABLE,
+ NULL,
+ NULL);
+ }
+}
+
+void
+pk_backend_get_update_detail(PkBackend *backend, PkBackendJob *job, gchar **package_ids)
+{
+ pk_backend_job_thread_create(job, pk_backend_get_update_detail_thread, NULL, NULL);
+}
+
+PkBitfield
+pk_backend_get_filters (PkBackend *backend)
+{
+ return pk_bitfield_from_enums (
+ PK_FILTER_ENUM_INSTALLED, PK_FILTER_ENUM_NOT_INSTALLED,
+ PK_FILTER_ENUM_APPLICATION, PK_FILTER_ENUM_NOT_APPLICATION,
+ -1);
+}
diff --git a/backend/pkgtools.cc b/backend/pkgtools.cc
new file mode 100644
index 0000000..083036a
--- /dev/null
+++ b/backend/pkgtools.cc
@@ -0,0 +1,175 @@
+#include <curl/curl.h>
+#include <sqlite3.h>
+#include "pkgtools.h"
+#include "utils.h"
+
+namespace slack {
+
+/**
+ * slack::Pkgtools::download:
+ * @job: A #PkBackendJob.
+ * @dest_dir_name: Destination directory.
+ * @pkg_name: Package name.
+ *
+ * Download a package.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ **/
+gboolean
+Pkgtools::download (PkBackendJob *job,
+ gchar *dest_dir_name, gchar *pkg_name) noexcept
+{
+ gchar *dest_filename, *source_url;
+ gboolean ret = FALSE;
+ sqlite3_stmt *statement = NULL;
+ CURL *curl = NULL;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT location, (full_name || '.' || ext) FROM pkglist "
+ "WHERE name LIKE @name AND repo_order = @repo_order",
+ -1,
+ &statement,
+ NULL) != SQLITE_OK))
+ return FALSE;
+
+ sqlite3_bind_text(statement, 1, pkg_name, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int(statement, 2, this->get_order ());
+
+ if (sqlite3_step(statement) == SQLITE_ROW)
+ {
+ dest_filename = g_build_filename(dest_dir_name, sqlite3_column_text(statement, 1), NULL);
+ source_url = g_strconcat(this->get_mirror (),
+ sqlite3_column_text(statement, 0),
+ "/",
+ sqlite3_column_text(statement, 1),
+ NULL);
+
+ if (!g_file_test(dest_filename, G_FILE_TEST_EXISTS))
+ {
+ if (get_file(&curl, source_url, dest_filename) == CURLE_OK)
+ {
+ ret = TRUE;
+ }
+ }
+ else
+ {
+ ret = TRUE;
+ }
+
+ if (curl)
+ {
+ curl_easy_cleanup(curl);
+ }
+ g_free(source_url);
+ g_free(dest_filename);
+ }
+ sqlite3_finalize(statement);
+
+ return ret;
+}
+
+/**
+ * slack::Pkgtools::install:
+ * @job: A #PkBackendJob.
+ * @pkg_name: Package name.
+ *
+ * Install a package.
+ **/
+void
+Pkgtools::install (PkBackendJob *job, gchar *pkg_name) noexcept
+{
+ gchar *pkg_filename, *cmd_line;
+ sqlite3_stmt *statement = NULL;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ if ((sqlite3_prepare_v2(job_data->db,
+ "SELECT (full_name || '.' || ext) FROM pkglist "
+ "WHERE name LIKE @name AND repo_order = @repo_order",
+ -1,
+ &statement,
+ NULL) != SQLITE_OK))
+ {
+ return;
+ }
+
+ sqlite3_bind_text(statement, 1, pkg_name, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int(statement, 2, this->get_order ());
+
+ if (sqlite3_step(statement) == SQLITE_ROW)
+ {
+ pkg_filename = g_build_filename(LOCALSTATEDIR,
+ "cache",
+ "PackageKit",
+ "downloads",
+ sqlite3_column_text(statement, 0),
+ NULL);
+ cmd_line = g_strconcat("/sbin/upgradepkg --install-new ", pkg_filename, NULL);
+ g_spawn_command_line_sync(cmd_line, NULL, NULL, NULL, NULL);
+ g_free(cmd_line);
+
+ g_free(pkg_filename);
+ }
+ sqlite3_finalize(statement);
+}
+
+Pkgtools::~Pkgtools () noexcept
+{
+}
+
+/**
+ * slack::Pkgtools::get_name:
+ *
+ * Retrieves the repository name.
+ *
+ * Returns: Repository name.
+ **/
+const gchar *
+Pkgtools::get_name () const noexcept
+{
+ return this->name;
+}
+
+/**
+ * slack::Pkgtools::get_mirror:
+ *
+ * Retrieves the repository mirror.
+ *
+ * Returns: Repository mirror.
+ **/
+const gchar *
+Pkgtools::get_mirror () const noexcept
+{
+ return this->mirror;
+}
+
+/**
+ * slack::Pkgtools::get_order:
+ *
+ * Retrieves the repository order.
+ *
+ * Returns: Repository order.
+ **/
+guint8
+Pkgtools::get_order () const noexcept
+{
+ return this->order;
+}
+
+/**
+ * slack::Pkgtools:is_blacklisted:
+ * @pkg: Package name to check for.
+ *
+ * Checks whether a package is blacklisted.
+ *
+ * Returns: %TRUE if the package is blacklisted, %FALSE otherwise.
+ **/
+gboolean
+Pkgtools::is_blacklisted (const gchar *pkg) const noexcept
+{
+ return this->blacklist
+ && g_regex_match (this->blacklist,
+ pkg, static_cast<GRegexMatchFlags> (0), NULL);
+}
+
+}
diff --git a/backend/pkgtools.h b/backend/pkgtools.h
new file mode 100644
index 0000000..cedf315
--- /dev/null
+++ b/backend/pkgtools.h
@@ -0,0 +1,36 @@
+#ifndef __SLACK_PKGTOOLS_H
+#define __SLACK_PKGTOOLS_H
+
+#include <glib-object.h>
+#include <pk-backend.h>
+
+namespace slack {
+
+class Pkgtools
+{
+public:
+ const gchar *get_name () const noexcept;
+ const gchar *get_mirror () const noexcept;
+ guint8 get_order () const noexcept;
+ gboolean is_blacklisted (const gchar *pkg) const noexcept;
+
+ virtual ~Pkgtools () noexcept;
+
+ gboolean download (PkBackendJob *job,
+ gchar *dest_dir_name, gchar *pkg_name) noexcept;
+ void install (PkBackendJob *job, gchar *pkg_name) noexcept;
+
+ virtual GSList *collect_cache_info (const gchar *tmpl) noexcept = 0;
+ virtual void generate_cache (PkBackendJob *job,
+ const gchar *tmpl) noexcept = 0;
+
+protected:
+ gchar *name = NULL;
+ gchar *mirror = NULL;
+ guint8 order;
+ GRegex *blacklist = NULL;
+};
+
+}
+
+#endif /* __SLACK_PKGTOOLS_H */
diff --git a/backend/slackpkg.cc b/backend/slackpkg.cc
new file mode 100644
index 0000000..d10fac2
--- /dev/null
+++ b/backend/slackpkg.cc
@@ -0,0 +1,525 @@
+#include <bzlib.h>
+#include <sqlite3.h>
+#include <stdlib.h>
+#include <string.h>
+#include "slackpkg.h"
+#include "utils.h"
+
+namespace slack {
+
+GHashTable *Slackpkg::cat_map = NULL;
+
+/*
+ * slack::Slackpkg::manifest:
+ * @job: a #PkBackendJob.
+ * @tmpl: temporary directory.
+ * @filename: manifest filename
+ *
+ * Parse the manifest file and save the file list in the database.
+ */
+void
+Slackpkg::manifest (PkBackendJob *job,
+ const gchar *tmpl, gchar *filename) noexcept
+{
+ FILE *manifest;
+ gint err, read_len;
+ guint pos;
+ gchar buf[max_buf_size], *path, *pkg_filename, *rest = NULL, *start;
+ gchar *full_name = NULL;
+ gchar **line, **lines;
+ BZFILE *manifest_bz2;
+ GRegex *pkg_expr = NULL, *file_expr = NULL;
+ GMatchInfo *match_info;
+ sqlite3_stmt *statement = NULL;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ path = g_build_filename(tmpl,
+ this->get_name (),
+ filename,
+ NULL);
+ manifest = fopen(path, "rb");
+ g_free(path);
+
+ if (!manifest)
+ {
+ return;
+ }
+ if (!(manifest_bz2 = BZ2_bzReadOpen(&err, manifest, 0, 0, NULL, 0)))
+ {
+ goto out;
+ }
+
+ /* Prepare regular expressions */
+ pkg_expr = g_regex_new("^\\|\\|[[:blank:]]+Package:[[:blank:]]+.+\\/(.+)\\.(t[blxg]z$)?",
+ static_cast<GRegexCompileFlags> (G_REGEX_OPTIMIZE | G_REGEX_DUPNAMES),
+ static_cast<GRegexMatchFlags> (0),
+ NULL);
+ file_expr = g_regex_new("^[-bcdlps][-r][-w][-xsS][-r][-w][-xsS][-r][-w]"
+ "[-xtT][[:space:]][^[:space:]]+[[:space:]]+"
+ "[[:digit:]]+[[:space:]][[:digit:]-]+[[:space:]]"
+ "[[:digit:]:]+[[:space:]](?!install\\/|\\.)(.*)",
+ static_cast<GRegexCompileFlags> (G_REGEX_OPTIMIZE | G_REGEX_DUPNAMES),
+ static_cast<GRegexMatchFlags> (0),
+ NULL);
+ if (!(file_expr) || !(pkg_expr))
+ {
+ goto out;
+ }
+
+ /* Prepare SQL statements */
+ if (sqlite3_prepare_v2(job_data->db,
+ "INSERT INTO filelist (full_name, filename) VALUES (@full_name, @filename)",
+ -1,
+ &statement,
+ NULL) != SQLITE_OK)
+ {
+ goto out;
+ }
+
+ sqlite3_exec(job_data->db, "BEGIN TRANSACTION", NULL, NULL, NULL);
+ while ((read_len = BZ2_bzRead(&err, manifest_bz2, buf, max_buf_size - 1)))
+ {
+ if ((err != BZ_OK) && (err != BZ_STREAM_END))
+ {
+ break;
+ }
+ buf[read_len] = '\0';
+
+ /* Split the read text into lines */
+ lines = g_strsplit(buf, "\n", 0);
+ if (rest)
+ { /* Add to the first line rest characters from the previous read operation */
+ start = lines[0];
+ lines[0] = g_strconcat(rest, lines[0], NULL);
+ g_free(start);
+ g_free(rest);
+ }
+ if (err != BZ_STREAM_END) /* The last line can be incomplete */
+ {
+ pos = g_strv_length(lines) - 1;
+ rest = lines[pos];
+ lines[pos] = NULL;
+ }
+ for (line = lines; *line; line++)
+ {
+ if (g_regex_match(pkg_expr, *line, static_cast<GRegexMatchFlags> (0), &match_info))
+ {
+ if (g_match_info_get_match_count(match_info) > 2)
+ { /* If the extension matches */
+ g_free(full_name);
+ full_name = g_match_info_fetch(match_info, 1);
+ }
+ else
+ {
+ full_name = NULL;
+ }
+ }
+ g_match_info_free(match_info);
+
+ match_info = NULL;
+ if (full_name && g_regex_match(file_expr, *line, static_cast<GRegexMatchFlags> (0), &match_info))
+ {
+ pkg_filename = g_match_info_fetch(match_info, 1);
+ sqlite3_bind_text(statement, 1, full_name, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 2, pkg_filename, -1, SQLITE_TRANSIENT);
+ sqlite3_step(statement);
+ sqlite3_clear_bindings(statement);
+ sqlite3_reset(statement);
+ g_free(pkg_filename);
+ }
+ g_match_info_free(match_info);
+ }
+ g_strfreev(lines);
+ }
+
+ sqlite3_exec(job_data->db, "END TRANSACTION", NULL, NULL, NULL);
+ g_free(full_name);
+ BZ2_bzReadClose(&err, manifest_bz2);
+
+out:
+ sqlite3_finalize(statement);
+ if (file_expr)
+ {
+ g_regex_unref(file_expr);
+ }
+ if (pkg_expr)
+ {
+ g_regex_unref(pkg_expr);
+ }
+ fclose(manifest);
+}
+
+/**
+ * slack::Slackpkg::collect_cache_info:
+ * @tmpl: temporary directory for downloading the files.
+ *
+ * Download files needed to get the information like the list of packages
+ * in available repositories, updates, package descriptions and so on.
+ *
+ * Returns: List of files needed for building the cache.
+ **/
+GSList *
+Slackpkg::collect_cache_info (const gchar *tmpl) noexcept
+{
+ CURL *curl = NULL;
+ gchar **source_dest;
+ GSList *file_list = NULL;
+ GFile *tmp_dir, *repo_tmp_dir;
+
+ /* Create the temporary directory for the repository */
+ tmp_dir = g_file_new_for_path(tmpl);
+ repo_tmp_dir = g_file_get_child(tmp_dir, this->get_name ());
+ g_file_make_directory(repo_tmp_dir, NULL, NULL);
+
+ /* Download PACKAGES.TXT. These files are most important, break if some of them couldn't be found */
+ for (gchar **cur_priority = this->priority; *cur_priority; cur_priority++)
+ {
+ source_dest = static_cast<gchar **> (g_malloc_n(3, sizeof(gchar *)));
+ source_dest[0] = g_strconcat(this->get_mirror (),
+ *cur_priority,
+ "/PACKAGES.TXT",
+ NULL);
+ source_dest[1] = g_build_filename(tmpl,
+ this->get_name (),
+ "PACKAGES.TXT",
+ NULL);
+ source_dest[2] = NULL;
+
+ if (get_file(&curl, source_dest[0], NULL) == CURLE_OK)
+ {
+ file_list = g_slist_prepend(file_list, source_dest);
+ }
+ else
+ {
+ g_strfreev(source_dest);
+ g_slist_free_full(file_list, (GDestroyNotify)g_strfreev);
+ goto out;
+ }
+
+ /* Download file lists if available */
+ source_dest = static_cast<gchar **> (g_malloc_n(3, sizeof(gchar *)));
+ source_dest[0] = g_strconcat(this->get_mirror (),
+ *cur_priority,
+ "/MANIFEST.bz2",
+ NULL);
+ source_dest[1] = g_strconcat(tmpl,
+ "/", this->get_name (),
+ "/", *cur_priority, "-MANIFEST.bz2",
+ NULL);
+ source_dest[2] = NULL;
+ if (get_file(&curl, source_dest[0], NULL) == CURLE_OK)
+ {
+ file_list = g_slist_prepend(file_list, source_dest);
+ }
+ else
+ {
+ g_strfreev(source_dest);
+ }
+ }
+out:
+ g_object_unref(repo_tmp_dir);
+ g_object_unref(tmp_dir);
+
+ if (curl)
+ {
+ curl_easy_cleanup(curl);
+ }
+ return file_list;
+}
+
+/**
+ * slack::Slackpkg::generate_cache:
+ * @job: A #PkBackendJob.
+ * @tmpl: temporary directory for downloading the files.
+ *
+ * Download files needed to get the information like the list of packages
+ * in available repositories, updates, package descriptions and so on.
+ *
+ * Returns: List of files needed for building the cache.
+ **/
+void
+Slackpkg::generate_cache (PkBackendJob *job, const gchar *tmpl) noexcept
+{
+ gchar **pkg_tokens = NULL;
+ gchar *query = NULL, *filename = NULL, *location = NULL, *summary = NULL, *line, *packages_txt;
+ guint pkg_compressed = 0, pkg_uncompressed = 0;
+ gushort pkg_name_len;
+ GString *desc;
+ GFile *list_file;
+ GFileInputStream *fin = NULL;
+ GDataInputStream *data_in = NULL;
+ sqlite3_stmt *insert_statement = NULL, *update_statement = NULL, *insert_default_statement = NULL, *statement;
+ auto job_data = static_cast<JobData *> (pk_backend_job_get_user_data(job));
+
+ /* Check if the temporary directory for this repository exists, then the file metadata have to be generated */
+ packages_txt = g_build_filename(tmpl,
+ this->get_name (),
+ "PACKAGES.TXT",
+ NULL);
+ list_file = g_file_new_for_path(packages_txt);
+ fin = g_file_read(list_file, NULL, NULL);
+ g_object_unref(list_file);
+ g_free(packages_txt);
+ if (!fin)
+ {
+ goto out;
+ }
+ /* Remove the old entries from this repository */
+ if (sqlite3_prepare_v2(job_data->db,
+ "DELETE FROM repos WHERE repo LIKE @repo",
+ -1,
+ &statement,
+ NULL) == SQLITE_OK)
+ {
+ sqlite3_bind_text(statement,
+ 1,
+ this->get_name (),
+ -1,
+ SQLITE_TRANSIENT);
+ sqlite3_step(statement);
+ sqlite3_finalize(statement);
+ }
+ if (sqlite3_prepare_v2(job_data->db,
+ "INSERT INTO repos (repo_order, repo) VALUES (@repo_order, @repo)",
+ -1,
+ &statement,
+ NULL) != SQLITE_OK)
+ {
+ goto out;
+ }
+ sqlite3_bind_int(statement, 1, this->get_order ());
+ sqlite3_bind_text(statement,
+ 2,
+ this->get_name (),
+ -1,
+ SQLITE_TRANSIENT);
+ sqlite3_step(statement);
+ sqlite3_finalize(statement);
+
+ /* Insert new records */
+ if ((sqlite3_prepare_v2(job_data->db,
+ "INSERT OR REPLACE INTO pkglist (full_name, ver, arch, ext, location, "
+ "summary, desc, compressed, uncompressed, name, repo_order, cat) "
+ "VALUES (@full_name, @ver, @arch, @ext, @location, @summary, "
+ "@desc, @compressed, @uncompressed, @name, @repo_order, @cat)",
+ -1,
+ &insert_statement,
+ NULL) != SQLITE_OK)
+ || (sqlite3_prepare_v2(job_data->db,
+ "INSERT OR REPLACE INTO pkglist (full_name, ver, arch, ext, location, "
+ "summary, desc, compressed, uncompressed, name, repo_order) "
+ "VALUES (@full_name, @ver, @arch, @ext, @location, @summary, "
+ "@desc, @compressed, @uncompressed, @name, @repo_order)",
+ -1,
+ &insert_default_statement,
+ NULL) != SQLITE_OK))
+ {
+ goto out;
+ }
+ query = sqlite3_mprintf("UPDATE pkglist SET full_name = @full_name, ver = @ver, arch = @arch, "
+ "ext = @ext, location = @location, summary = @summary, "
+ "desc = @desc, compressed = @compressed, uncompressed = @uncompressed "
+ "WHERE name LIKE @name AND repo_order = %u",
+ this->get_order ());
+ if (sqlite3_prepare_v2(job_data->db, query, -1, &update_statement, NULL) != SQLITE_OK)
+ {
+ goto out;
+ }
+
+ data_in = g_data_input_stream_new(G_INPUT_STREAM(fin));
+ desc = g_string_new("");
+
+ sqlite3_exec(job_data->db, "BEGIN TRANSACTION", NULL, NULL, NULL);
+
+ while ((line = g_data_input_stream_read_line(data_in, NULL, NULL, NULL)))
+ {
+ if (!strncmp(line, "PACKAGE NAME: ", 15))
+ {
+ filename = g_strdup(line + 15);
+ if (this->is_blacklisted (filename))
+ {
+ g_free(filename);
+ filename = NULL;
+ }
+ }
+ else if (filename && !strncmp(line, "PACKAGE LOCATION: ", 19))
+ {
+ location = g_strdup(line + 21); /* Exclude ./ at the path beginning */
+ }
+ else if (filename && !strncmp(line, "PACKAGE SIZE (compressed): ", 28))
+ {
+ /* Remove the unit (kilobytes) */
+ pkg_compressed = atoi(g_strndup(line + 28, strlen(line + 28) - 2)) * 1024;
+ }
+ else if (filename && !strncmp(line, "PACKAGE SIZE (uncompressed): ", 30))
+ {
+ /* Remove the unit (kilobytes) */
+ pkg_uncompressed = atoi(g_strndup(line + 30, strlen(line + 30) - 2)) * 1024;
+ }
+ else if (filename && !g_strcmp0(line, "PACKAGE DESCRIPTION:"))
+ {
+ g_free(line);
+ line = g_data_input_stream_read_line(data_in, NULL, NULL, NULL); /* Short description */
+
+ summary = g_strstr_len(line, -1, "(");
+ if (summary) /* Else summary = NULL */
+ {
+ summary = g_strndup(summary + 1, strlen(summary) - 2); /* Without ( ) */
+ }
+ pkg_tokens = split_package_name(filename);
+ pkg_name_len = strlen(pkg_tokens[0]); /* Description begins with pkg_name: */
+ }
+ else if (filename && !strncmp(line, pkg_tokens[0], pkg_name_len))
+ {
+ g_string_append(desc, line + pkg_name_len + 1);
+ }
+ else if (filename && !g_strcmp0(line, ""))
+ {
+ if (g_strcmp0(location, "patches/packages")) /* Insert a new package */
+ {
+ /* Get the package group based on its location */
+ const char *cat = g_strrstr(location, "/");
+ if (cat) /* Else cat = NULL */
+ {
+ cat = static_cast<const char *> (g_hash_table_lookup(cat_map, cat + 1));
+ }
+ if (cat)
+ {
+ statement = insert_statement;
+ sqlite3_bind_text(insert_statement, 12, cat, -1, SQLITE_TRANSIENT);
+ }
+ else
+ {
+ statement = insert_default_statement;
+ }
+ sqlite3_bind_int(statement, 11, this->get_order ());
+ }
+ else /* Update package information if it is a patch */
+ {
+ statement = update_statement;
+ }
+ sqlite3_bind_text(statement, 1, pkg_tokens[3], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 2, pkg_tokens[1], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 3, pkg_tokens[2], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 4, pkg_tokens[4], -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 5, location, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 6, summary, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_text(statement, 7, desc->str, -1, SQLITE_TRANSIENT);
+ sqlite3_bind_int(statement, 8, pkg_compressed);
+ sqlite3_bind_int(statement, 9, pkg_uncompressed);
+ sqlite3_bind_text(statement, 10, pkg_tokens[0], -1, SQLITE_TRANSIENT);
+
+ sqlite3_step(statement);
+ sqlite3_clear_bindings(statement);
+ sqlite3_reset(statement);
+
+ /* Reset for the next package */
+ g_strfreev(pkg_tokens);
+ g_free(filename);
+ g_free(location);
+ g_free(summary);
+ filename = location = summary = NULL;
+ g_string_assign(desc, "");
+ pkg_compressed = pkg_uncompressed = 0;
+ }
+ g_free(line);
+ }
+ sqlite3_exec(job_data->db, "END TRANSACTION", NULL, NULL, NULL);
+
+ g_string_free(desc, TRUE);
+ g_object_unref(data_in);
+
+ /* Parse MANIFEST.bz2 */
+ for (gchar **p = this->priority; *p; p++)
+ {
+ filename = g_strconcat(*p, "-MANIFEST.bz2", NULL);
+ manifest (job, tmpl, filename);
+ g_free(filename);
+ }
+out:
+ sqlite3_finalize(update_statement);
+ sqlite3_free(query);
+ sqlite3_finalize(insert_default_statement);
+ sqlite3_finalize(insert_statement);
+
+ if (fin)
+ {
+ g_object_unref(fin);
+ }
+}
+
+Slackpkg::~Slackpkg () noexcept
+{
+ if (this->blacklist)
+ {
+ g_regex_unref (this->blacklist);
+ }
+
+ g_free (this->name);
+ g_free (this->mirror);
+ if (this->priority)
+ {
+ g_strfreev (this->priority);
+ }
+}
+
+/**
+ * slack::Slackpkg::Slackpkg:
+ * @name: Repository name.
+ * @mirror: Repository mirror.
+ * @order: Repository order.
+ * @blacklist: Blacklist.
+ * @priority: Groups priority.
+ *
+ * Constructor.
+ *
+ * Returns: New #slack::Slackpkg.
+ **/
+Slackpkg::Slackpkg (const gchar *name, const gchar *mirror,
+ guint8 order, const gchar *blacklist, gchar **priority) noexcept
+{
+ GRegex *regex;
+
+ if (blacklist)
+ {
+ regex = static_cast<GRegex *> (g_regex_new (blacklist,
+ G_REGEX_OPTIMIZE, static_cast<GRegexMatchFlags> (0), NULL));
+ }
+ else
+ {
+ regex = NULL;
+ }
+
+ this->name = g_strdup (name);
+ this->mirror = g_strdup (mirror);
+
+ this->order = order;
+
+ this->blacklist = regex;
+
+ this->priority = priority;
+
+ // Initialize category map
+ if (cat_map == NULL)
+ {
+ cat_map = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert (cat_map, (gpointer) "a", (gpointer) "system");
+ g_hash_table_insert (cat_map, (gpointer) "ap", (gpointer) "admin-tools");
+ g_hash_table_insert (cat_map, (gpointer) "d", (gpointer) "programming");
+ g_hash_table_insert (cat_map, (gpointer) "e", (gpointer) "programming");
+ g_hash_table_insert (cat_map, (gpointer) "f", (gpointer) "documentation");
+ g_hash_table_insert (cat_map, (gpointer) "k", (gpointer) "system");
+ g_hash_table_insert (cat_map, (gpointer) "kde", (gpointer) "desktop-kde");
+ g_hash_table_insert (cat_map, (gpointer) "kdei", (gpointer) "localization");
+ g_hash_table_insert (cat_map, (gpointer) "l", (gpointer) "system");
+ g_hash_table_insert (cat_map, (gpointer) "n", (gpointer) "network");
+ g_hash_table_insert (cat_map, (gpointer) "t", (gpointer) "publishing");
+ g_hash_table_insert (cat_map, (gpointer) "tcl", (gpointer) "system");
+ g_hash_table_insert (cat_map, (gpointer) "x", (gpointer) "desktop-other");
+ g_hash_table_insert (cat_map, (gpointer) "xap", (gpointer) "accessories");
+ g_hash_table_insert (cat_map, (gpointer) "xfce", (gpointer) "desktop-xfce");
+ g_hash_table_insert (cat_map, (gpointer) "y", (gpointer) "games");
+ }
+}
+
+}
diff --git a/backend/slackpkg.h b/backend/slackpkg.h
new file mode 100644
index 0000000..3ba60d6
--- /dev/null
+++ b/backend/slackpkg.h
@@ -0,0 +1,30 @@
+#ifndef __SLACK_SLACKPKG_H
+#define __SLACK_SLACKPKG_H
+
+#include <cstddef>
+#include "pkgtools.h"
+
+namespace slack {
+
+class Slackpkg final : public Pkgtools
+{
+public:
+ Slackpkg (const gchar *name, const gchar *mirror,
+ guint8 order, const gchar *blacklist, gchar **priority) noexcept;
+ ~Slackpkg () noexcept;
+
+ GSList *collect_cache_info (const gchar *tmpl) noexcept;
+ void generate_cache (PkBackendJob *job, const gchar *tmpl) noexcept;
+
+private:
+ static GHashTable *cat_map;
+ static const std::size_t max_buf_size = 8192;
+ gchar **priority = NULL;
+
+ void manifest (PkBackendJob *job,
+ const gchar *tmpl, gchar *filename) noexcept;
+};
+
+}
+
+#endif /* __SLACK_SLACKPKG_H */
diff --git a/backend/tests/definitions.cc b/backend/tests/definitions.cc
new file mode 100644
index 0000000..7906dd8
--- /dev/null
+++ b/backend/tests/definitions.cc
@@ -0,0 +1,92 @@
+#include "pk-backend.h"
+#include <pk-backend-job.h>
+
+gpointer
+pk_backend_job_get_user_data (PkBackendJob *job)
+{
+ return NULL;
+}
+
+void
+pk_backend_job_set_user_data (PkBackendJob *job, gpointer user_data)
+{
+}
+
+void
+pk_backend_job_set_allow_cancel (PkBackendJob *job, gboolean allow_cancel)
+{
+}
+
+void
+pk_backend_job_package (PkBackendJob *job,
+ PkInfoEnum info,
+ const gchar *package_id,
+ const gchar *summary)
+{
+}
+
+void
+pk_backend_job_set_status (PkBackendJob *job, PkStatusEnum status)
+{
+}
+
+void
+pk_backend_job_set_percentage (PkBackendJob *job, guint percentage)
+{
+}
+
+void
+pk_backend_job_error_code (PkBackendJob *job,
+ PkErrorEnum error_code, const gchar *format, ...)
+{
+}
+
+void
+pk_backend_job_files (PkBackendJob *job,
+ const gchar *package_id, gchar **files)
+{
+}
+
+void
+pk_backend_job_details (PkBackendJob *job,
+ const gchar *package_id,
+ const gchar *summary,
+ const gchar *license,
+ PkGroupEnum group,
+ const gchar *description,
+ const gchar *url,
+ gulong size,
+ guint64 download_size)
+{
+}
+
+void
+pk_backend_job_update_detail (PkBackendJob *job,
+ const gchar *package_id,
+ gchar **updates,
+ gchar **obsoletes,
+ gchar **vendor_urls,
+ gchar **bugzilla_urls,
+ gchar **cve_urls,
+ PkRestartEnum restart,
+ const gchar *update_text,
+ const gchar *changelog,
+ PkUpdateStateEnum state,
+ const gchar *issued,
+ const gchar *updated)
+{
+}
+
+gboolean
+pk_backend_job_thread_create (PkBackendJob *job,
+ PkBackendJobThreadFunc func,
+ gpointer user_data,
+ GDestroyNotify destroy_func)
+{
+ return FALSE;
+}
+
+gboolean pk_directory_remove_contents (const gchar *directory)
+{
+ return TRUE;
+}
diff --git a/backend/tests/dl-test.cc b/backend/tests/dl-test.cc
new file mode 100644
index 0000000..f4959fe
--- /dev/null
+++ b/backend/tests/dl-test.cc
@@ -0,0 +1,25 @@
+#include "dl.h"
+
+using namespace slack;
+
+static void
+slack_test_dl_construct()
+{
+ auto dl = new Dl ("some", "mirror", 1, NULL, NULL);
+
+ g_assert_cmpstr (dl->get_name (), ==, "some");
+ g_assert_cmpstr (dl->get_mirror (), ==, "mirror");
+ g_assert_cmpuint (dl->get_order (), ==, 1);
+ g_assert_false (dl->is_blacklisted ("pattern"));
+
+ delete dl;
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/slack/dl/construct", slack_test_dl_construct);
+
+ return g_test_run();
+}
diff --git a/backend/tests/job-test.cc b/backend/tests/job-test.cc
new file mode 100644
index 0000000..f784da3
--- /dev/null
+++ b/backend/tests/job-test.cc
@@ -0,0 +1,39 @@
+#include "job.h"
+
+using namespace slack;
+
+static void
+test_filter_package_installed ()
+{
+ PkBitfield filters = pk_bitfield_value (PK_FILTER_ENUM_INSTALLED);
+ g_assert_true (filter_package (filters, true));
+ g_assert_false (filter_package (filters, false));
+}
+
+static void
+test_filter_package_not_installed ()
+{
+ PkBitfield filters = pk_bitfield_value (PK_FILTER_ENUM_NOT_INSTALLED);
+ g_assert_true (filter_package (filters, false));
+ g_assert_false (filter_package (filters, true));
+}
+
+static void
+test_filter_package_none ()
+{
+ PkBitfield filters = pk_bitfield_value (PK_FILTER_ENUM_NONE);
+ g_assert_true (filter_package (filters, false));
+ g_assert_true (filter_package (filters, true));
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/slack/filter_package_installed", test_filter_package_installed);
+ g_test_add_func ("/slack/filter_package_not_installed", test_filter_package_not_installed);
+ g_test_add_func ("/slack/filter_package_none", test_filter_package_none);
+
+ return g_test_run ();
+}
diff --git a/backend/tests/meson.build b/backend/tests/meson.build
new file mode 100644
index 0000000..5ade48a
--- /dev/null
+++ b/backend/tests/meson.build
@@ -0,0 +1,55 @@
+bzip2_dep = dependency('bzip2')
+
+pk_slack_test_dependencies = [
+ packagekit_glib2_dep,
+ gmodule_dep,
+ sqlite3_dep,
+ bzip2_dep,
+ polkit_dep
+]
+
+pk_slack_test_cpp_args = [
+ '-DG_LOG_DOMAIN="PackageKit-Slackware"',
+ '-DLOCALSTATEDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('localstatedir'))),
+ '-DLIBDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('libdir'))),
+ '-DSYSCONFDIR="@0@"'.format(get_option('sysconfdir')),
+ '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()),
+ '-DLIBEXECDIR="@0@"'.format(join_paths(get_option('prefix'), get_option('libexecdir'))),
+ '-DPK_DB_DIR="."',
+]
+
+pk_slack_test_include_directories = [
+ include_directories('..'),
+ packagekit_src_include,
+]
+
+pk_slack_test_dl = executable('pk-slack-test-dl',
+ ['dl-test.cc', 'definitions.cc'],
+ link_with: packagekit_backend_slack_module,
+ include_directories: pk_slack_test_include_directories,
+ dependencies: pk_slack_test_dependencies,
+ cpp_args: pk_slack_test_cpp_args,
+ c_args: pk_slack_test_cpp_args
+)
+
+pk_slack_test_slackpkg = executable('pk-slack-test-slackpkg',
+ ['slackpkg-test.cc', 'definitions.cc'],
+ link_with: packagekit_backend_slack_module,
+ include_directories: pk_slack_test_include_directories,
+ dependencies: pk_slack_test_dependencies,
+ cpp_args: pk_slack_test_cpp_args,
+ c_args: pk_slack_test_cpp_args
+)
+
+pk_slack_test_job = executable('pk-slack-test-job',
+ ['job-test.cc', 'definitions.cc'],
+ link_with: packagekit_backend_slack_module,
+ include_directories: pk_slack_test_include_directories,
+ dependencies: pk_slack_test_dependencies,
+ cpp_args: pk_slack_test_cpp_args,
+ c_args: pk_slack_test_cpp_args
+)
+
+test('slack-dl', pk_slack_test_dl)
+test('slac-slackpkg', pk_slack_test_slackpkg)
+test('slack-job', pk_slack_test_job)
diff --git a/backend/tests/slackpkg-test.cc b/backend/tests/slackpkg-test.cc
new file mode 100644
index 0000000..09c4142
--- /dev/null
+++ b/backend/tests/slackpkg-test.cc
@@ -0,0 +1,25 @@
+#include "slackpkg.h"
+
+using namespace slack;
+
+static void
+slack_test_slackpkg_construct()
+{
+ auto slackpkg = new Slackpkg ("some", "mirror", 1, NULL, NULL);
+
+ g_assert_cmpstr(slackpkg->get_name (), ==, "some");
+ g_assert_cmpstr(slackpkg->get_mirror (), ==, "mirror");
+ g_assert_cmpuint(slackpkg->get_order (), ==, 1);
+ g_assert_false (slackpkg->is_blacklisted ("pattern"));
+
+ delete slackpkg;
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/slack/slackpkg/construct", slack_test_slackpkg_construct);
+
+ return g_test_run();
+}
diff --git a/backend/utils.cc b/backend/utils.cc
new file mode 100644
index 0000000..78778d0
--- /dev/null
+++ b/backend/utils.cc
@@ -0,0 +1,231 @@
+#include <sqlite3.h>
+#include <string.h>
+#include "utils.h"
+#include "pkgtools.h"
+
+namespace slack {
+
+/**
+ * slack::get_file:
+ * @curl: curl easy handle.
+ * @source_url: source url.
+ * @dest: destination.
+ *
+ * Download the file.
+ *
+ * Returns: CURLE_OK (zero) on success, non-zero otherwise.
+ **/
+CURLcode
+get_file (CURL **curl, gchar *source_url, gchar *dest)
+{
+ gchar *dest_dir_name;
+ FILE *fout = NULL;
+ CURLcode ret;
+ glong response_code;
+
+ if ((*curl == NULL) && (!(*curl = curl_easy_init())))
+ {
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ curl_easy_setopt(*curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(*curl, CURLOPT_URL, source_url);
+
+ if (dest == NULL)
+ {
+ curl_easy_setopt(*curl, CURLOPT_NOBODY, 1L);
+ curl_easy_setopt(*curl, CURLOPT_HEADER, 1L);
+ ret = curl_easy_perform(*curl);
+ curl_easy_getinfo(*curl, CURLINFO_RESPONSE_CODE, &response_code);
+
+ if (response_code != 200)
+ {
+ ret = CURLE_REMOTE_FILE_NOT_FOUND;
+ }
+ }
+ else
+ {
+ if (g_file_test(dest, G_FILE_TEST_IS_DIR))
+ {
+ dest_dir_name = dest;
+ dest = g_strconcat(dest_dir_name, g_strrstr(source_url, "/"), NULL);
+ g_free(dest_dir_name);
+ }
+ if ((fout = fopen(dest, "ab")) == NULL)
+ {
+ return CURLE_WRITE_ERROR;
+ }
+ curl_easy_setopt(*curl, CURLOPT_WRITEDATA, fout);
+ ret = curl_easy_perform(*curl);
+ }
+ curl_easy_reset(*curl);
+ if (fout != NULL)
+ {
+ fclose(fout);
+ }
+ return ret;
+}
+
+/**
+ * slack::split_package_name:
+ * Got the name of a package, without version-arch-release data.
+ **/
+gchar **
+split_package_name (const gchar *pkg_filename)
+{
+ gchar *pkg_full_name;
+ gchar **pkg_tokens;
+
+ g_return_val_if_fail(pkg_filename != NULL, NULL);
+
+ gint len = strlen(pkg_filename);
+ if (len < 4)
+ {
+ return NULL;
+ }
+
+ if (pkg_filename[len - 4] == '.')
+ {
+ pkg_tokens = static_cast<gchar **> (g_malloc_n (6, sizeof (gchar *)));
+
+ /* Full name without extension */
+ len -= 4;
+ pkg_full_name = g_strndup (pkg_filename, len);
+ pkg_tokens[3] = g_strdup (pkg_full_name);
+
+ /* The last 3 characters should be the file extension */
+ pkg_tokens[4] = g_strdup (pkg_filename + len + 1);
+ pkg_tokens[5] = NULL;
+ }
+ else
+ {
+ pkg_tokens = static_cast<gchar **> (g_malloc_n (4, sizeof (gchar *)));
+ pkg_full_name = g_strdup (pkg_filename);
+ pkg_tokens[3] = NULL;
+ }
+
+ /* Reverse all of the bytes in the package filename to get the name, version and the architecture */
+ g_strreverse (pkg_full_name);
+ gchar **reversed_tokens = g_strsplit (pkg_full_name, "-", 4);
+ pkg_tokens[0] = g_strreverse (reversed_tokens[3]); /* Name */
+ pkg_tokens[1] = g_strreverse (reversed_tokens[2]); /* Version */
+ pkg_tokens[2] = g_strreverse (reversed_tokens[1]); /* Architecture */
+
+ g_free (reversed_tokens[0]); /* Build number */
+ g_free (reversed_tokens);
+ g_free (pkg_full_name);
+
+ return pkg_tokens;
+}
+
+/**
+ * slack::is_installed:
+ * Checks if a package is already installed in the system.
+ *
+ * Params:
+ * pkg_fullname = Package name should be looked for.
+ *
+ * Returns: PK_INFO_ENUM_INSTALLING if pkg_fullname is already installed,
+ * PK_INFO_ENUM_UPDATING if an elder version of pkg_fullname is
+ * installed, PK_INFO_ENUM_UNKNOWN if pkg_fullname is malformed.
+ **/
+PkInfoEnum
+is_installed (const gchar *pkg_fullname)
+{
+ GFileEnumerator *pkg_metadata_enumerator;
+ GFileInfo *pkg_metadata_file_info;
+ GFile *pkg_metadata_dir;
+ PkInfoEnum ret = PK_INFO_ENUM_INSTALLING;
+ const gchar *it;
+ guint8 dashes = 0;
+ ptrdiff_t pkg_name;
+
+ g_return_val_if_fail(pkg_fullname != NULL, PK_INFO_ENUM_UNKNOWN);
+
+ // We want to find the package name without version for the package we're
+ // looking for.
+ g_debug("Looking if %s is installed", pkg_fullname);
+
+ for (it = pkg_fullname + strlen(pkg_fullname); it != pkg_fullname; --it)
+ {
+ if (*it == '-')
+ {
+ if (dashes == 2)
+ {
+ break;
+ }
+ ++dashes;
+ }
+ }
+ if (dashes < 2)
+ {
+ return PK_INFO_ENUM_UNKNOWN;
+ }
+ pkg_name = it - pkg_fullname;
+
+ // Read the package metadata directory and comprare all installed packages
+ // with ones in the cache.
+ pkg_metadata_dir = g_file_new_for_path("/var/log/packages");
+ if (!(pkg_metadata_enumerator = g_file_enumerate_children(pkg_metadata_dir,
+ "standard::name",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL)))
+ {
+ g_object_unref(pkg_metadata_dir);
+ return PK_INFO_ENUM_UNKNOWN;
+ }
+
+ while ((pkg_metadata_file_info = g_file_enumerator_next_file(pkg_metadata_enumerator, NULL, NULL)))
+ {
+ const gchar *dir = g_file_info_get_name(pkg_metadata_file_info);
+ dashes = 0;
+
+ if (strcmp(dir, pkg_fullname) == 0)
+ {
+ ret = PK_INFO_ENUM_INSTALLED;
+ }
+ else
+ {
+ for (it = dir + strlen(dir); it != dir; --it)
+ {
+ if (*it == '-')
+ {
+ if (dashes == 2)
+ {
+ break;
+ }
+ ++dashes;
+ }
+ }
+ if (pkg_name == (it - dir) && strncmp(pkg_fullname, dir, pkg_name) == 0)
+ {
+ ret = PK_INFO_ENUM_UPDATING;
+ }
+
+ }
+ g_object_unref(pkg_metadata_file_info);
+
+ if (ret != PK_INFO_ENUM_INSTALLING) /* If installed */
+ {
+ break;
+ }
+ }
+ g_object_unref(pkg_metadata_enumerator);
+ g_object_unref(pkg_metadata_dir);
+
+ return ret;
+}
+
+/**
+ * slack::cmp_repo:
+ **/
+gint
+cmp_repo (gconstpointer a, gconstpointer b)
+{
+ auto repo = static_cast<const Pkgtools *> (a);
+
+ return g_strcmp0 (repo->get_name (), (gchar *) b);
+}
+
+}
diff --git a/backend/utils.h b/backend/utils.h
new file mode 100644
index 0000000..b982429
--- /dev/null
+++ b/backend/utils.h
@@ -0,0 +1,32 @@
+#ifndef __SLACK_UTILS_H
+#define __SLACK_UTILS_H
+
+#include <curl/curl.h>
+#include <pk-backend.h>
+#include <pk-backend-job.h>
+
+namespace slack {
+
+struct JobData
+{
+ GObjectClass parent_class;
+
+ sqlite3 *db;
+ CURL *curl;
+};
+
+CURLcode get_file (CURL **curl, gchar *source_url, gchar *dest);
+
+gchar **split_package_name (const gchar *pkg_filename);
+
+PkInfoEnum is_installed (const gchar *pkg_fullname);
+
+extern "C" {
+
+gint cmp_repo (gconstpointer a, gconstpointer b);
+
+}
+
+}
+
+#endif /* __SLACK_UTILS_H */