appstream-generator/source/contentsstore.d
Matthias Klumpp ac7f31f414 Make a splitbuild with Meson and Ninja possible
This work is still in its earliest stages and requires a patched Meson.
When finished, it will finally allow us to have a sane build system for
asgen though and to compile even optimized code with ldc/gdc
dramatically faster.
2016-08-15 20:16:29 +02:00

335 lines
9.2 KiB
D

/*
* Copyright (C) 2016 Matthias Klumpp <matthias@tenstral.net>
*
* Licensed under the GNU Lesser General Public License Version 3
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the license, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
import std.stdio;
import std.string;
import std.conv : to, octal;
import std.file : mkdirRecurse;
import std.array : appender, join, split, empty;
static import std.math;
import bindings.lmdb;
import config;
import logging;
/**
* Contains a cache about available files in packages.
* This is useful for finding icons and for re-scanning
* packages which become interesting later.
**/
class ContentsStore
{
private:
MDB_envp dbEnv;
MDB_dbi dbContents;
MDB_dbi dbIcons;
bool opened;
public:
this ()
{
opened = false;
}
~this ()
{
if (opened)
dbEnv.mdb_env_close ();
}
private void checkError (int rc, string msg)
{
if (rc != 0) {
import std.format;
throw new Exception (format ("%s[%s]: %s", msg, rc, mdb_strerror (rc).fromStringz));
}
}
void open (string dir)
{
int rc;
assert (!opened);
logDebug ("Opening contents cache.");
// ensure the cache directory exists
mkdirRecurse (dir);
rc = mdb_env_create (&dbEnv);
scope (success) opened = true;
scope (failure) dbEnv.mdb_env_close ();
checkError (rc, "mdb_env_create");
// We are going to use at max 2 sub-databases:
// contents and icons
rc = dbEnv.mdb_env_set_maxdbs (2);
checkError (rc, "mdb_env_set_maxdbs");
// set a huge map size to be futureproof.
// This means we're cruel to non-64bit users, but this
// software is supposed to be run on 64bit machines anyway.
auto mapsize = cast (size_t) std.math.pow (512L, 4);
rc = dbEnv.mdb_env_set_mapsize (mapsize);
checkError (rc, "mdb_env_set_mapsize");
// open database
rc = dbEnv.mdb_env_open (dir.toStringz (), MDB_NOMETASYNC, octal!755);
checkError (rc, "mdb_env_open");
// open sub-databases in the environment
MDB_txnp txn;
rc = dbEnv.mdb_txn_begin (null, 0, &txn);
checkError (rc, "mdb_txn_begin");
scope (failure) txn.mdb_txn_abort ();
// contains a full list of all contents
rc = txn.mdb_dbi_open ("contents", MDB_CREATE, &dbContents);
checkError (rc, "open contents database");
// contains list of icon files and related data
// the contents sub-database exists only to allow building instances
// of IconHandler much faster.
rc = txn.mdb_dbi_open ("icondata", MDB_CREATE, &dbIcons);
checkError (rc, "open icon-info database");
rc = txn.mdb_txn_commit ();
checkError (rc, "mdb_txn_commit");
}
void open (Config conf)
{
import std.path : buildPath;
this.open (buildPath (conf.databaseDir, "contents"));
}
private MDB_val makeDbValue (string data)
{
import core.stdc.string : strlen;
MDB_val mval;
auto cdata = data.toStringz ();
mval.mv_size = char.sizeof * strlen (cdata) + 1;
mval.mv_data = cast(void *) cdata;
return mval;
}
private MDB_txnp newTransaction (uint flags = 0)
{
int rc;
MDB_txnp txn;
rc = dbEnv.mdb_txn_begin (null, flags, &txn);
checkError (rc, "mdb_txn_begin");
return txn;
}
private void commitTransaction (MDB_txnp txn)
{
auto rc = txn.mdb_txn_commit ();
checkError (rc, "mdb_txn_commit");
}
private void quitTransaction (MDB_txnp txn)
{
if (txn is null)
return;
txn.mdb_txn_abort ();
}
/**
* Drop a package-id from the contents cache.
*/
void removePackage (string pkid)
{
MDB_val key;
key = makeDbValue (pkid);
auto txn = newTransaction ();
scope (success) commitTransaction (txn);
scope (failure) quitTransaction (txn);
auto res = txn.mdb_del (dbContents, &key, null);
checkError (res, "mdb_del (contents)");
res = txn.mdb_del (dbIcons, &key, null);
if (res != MDB_NOTFOUND)
checkError (res, "mdb_del (icons)");
}
bool packageExists (string pkid)
{
MDB_val dkey;
MDB_cursorp cur;
dkey = makeDbValue (pkid);
auto txn = newTransaction (MDB_RDONLY);
scope (exit) quitTransaction (txn);
auto res = txn.mdb_cursor_open (dbContents, &cur);
scope (exit) cur.mdb_cursor_close ();
checkError (res, "mdb_cursor_open");
res = cur.mdb_cursor_get (&dkey, null, MDB_SET);
if (res == MDB_NOTFOUND)
return false;
checkError (res, "mdb_cursor_get");
return true;
}
void addContents (string pkid, string[] contents)
{
MDB_val key, value;
// filter out icon filenames and filenames of icon-related stuff (e.g. theme.index)
auto iconInfo = appender!(string[]);
foreach (ref c; contents) {
if ((c.startsWith ("/usr/share/icons/")) ||
(c.startsWith ("/usr/share/pixmaps/"))) {
iconInfo ~= c;
}
}
immutable contentsStr = contents.join ("\n");
key = makeDbValue (pkid);
value = makeDbValue (contentsStr);
auto txn = newTransaction ();
scope (success) commitTransaction (txn);
scope (failure) quitTransaction (txn);
auto res = txn.mdb_put (dbContents, &key, &value, 0);
checkError (res, "mdb_put");
if (!iconInfo.data.empty) {
// we have icon information, store it too
immutable iconsStr = iconInfo.data.join ("\n");
value = makeDbValue (iconsStr);
res = txn.mdb_put (dbIcons, &key, &value, 0);
checkError (res, "mdb_put (icons)");
}
}
private string[string] getFilesMap (string[] pkids, MDB_dbi dbi)
{
MDB_cursorp cur;
auto txn = newTransaction (MDB_RDONLY);
scope (exit) quitTransaction (txn);
auto res = txn.mdb_cursor_open (dbi, &cur);
scope (exit) cur.mdb_cursor_close ();
checkError (res, "mdb_cursor_open");
string[string] pkgCMap;
MDB_val pkey;
MDB_val cval;
foreach (ref pkid; pkids) {
pkey = makeDbValue (pkid);
res = cur.mdb_cursor_get (&pkey, &cval, MDB_SET);
if (res == MDB_NOTFOUND)
continue;
checkError (res, "mdb_cursor_get");
auto data = fromStringz (cast(char*) cval.mv_data);
auto contents = to!string (data);
foreach (ref c; contents.split ("\n")) {
pkgCMap[c] = pkid;
}
}
return pkgCMap;
}
string[string] getContentsMap (string[] pkids)
{
return getFilesMap (pkids, dbContents);
}
string[string] getIconFilesMap (string[] pkids)
{
return getFilesMap (pkids, dbIcons);
}
string[] getContents (string pkid)
{
MDB_val pkey, cval;
MDB_cursorp cur;
pkey = makeDbValue (pkid);
auto txn = newTransaction (MDB_RDONLY);
scope (exit) quitTransaction (txn);
auto res = txn.mdb_cursor_open (dbContents, &cur);
scope (exit) cur.mdb_cursor_close ();
checkError (res, "mdb_cursor_open");
res = cur.mdb_cursor_get (&pkey, &cval, MDB_SET);
if (res == MDB_NOTFOUND)
return null;
checkError (res, "mdb_cursor_get");
auto data = fromStringz (cast(char*) cval.mv_data);
auto contentsStr = to!string (data);
return contentsStr.split ("\n");
}
void removePackagesNotInSet (bool[string] pkgSet)
{
MDB_cursorp cur;
auto txn = newTransaction ();
scope (success) commitTransaction (txn);
scope (failure) quitTransaction (txn);
auto res = txn.mdb_cursor_open (dbContents, &cur);
scope (exit) cur.mdb_cursor_close ();
checkError (res, "mdb_cursor_open (pkgcruft_contents)");
MDB_val pkey;
while (cur.mdb_cursor_get (&pkey, null, MDB_NEXT) == 0) {
immutable pkid = to!string (fromStringz (cast(char*) pkey.mv_data));
if (pkid in pkgSet)
continue;
// if we got here, the package is not in the set of valid packages,
// and we can remove it.
res = cur.mdb_cursor_del (0);
checkError (res, "mdb_del");
res = txn.mdb_del (dbIcons, &pkey, null);
if (res != MDB_NOTFOUND)
checkError (res, "mdb_del (icons)");
}
}
}