Commit 909aae68 authored by Lisa's avatar Lisa

Added a backend to perform all operations on cache.

parent 13baa589
......@@ -18,6 +18,7 @@ akabeioperation.cpp
akabeioperationrunner.cpp
akabeipackage.cpp
akabeilog.cpp
akabeicache.cpp
akabeirunnerrunnable_p.cpp
akabeivalidatorrunnable_p.cpp
md5.c
......@@ -48,6 +49,7 @@ akabeioperation.h
akabeioperationrunner.h
akabeipackage.h
akabeilog.h
akabeicache.h
akabeicore_global.h
SQLiteConnection.h
libarchive++/archivehandler.h
......
/*
* This file is part of the Chakra project
* The cache manager
Copyright (C) 2010 Lisa Vitolo <shainer@chakra-project.org>
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 "akabeicache.h"
#include "akabeidatabase.h"
#include "akabeipackage.h"
#include "akabeiconfig.h"
/*
* This is an utility class we use for sorting the cache entries according to some date/time.
*/
namespace Akabei
{
class CachePair
{
public:
CachePair(QDateTime key, QString value)
: m_key(key)
, m_value(value)
{}
/*
* qSort() needs this operator to function
*/
bool operator<(const CachePair& other) const
{
return (m_key < other.m_key); /* we do a date comparison */
}
QDateTime key() const
{
return m_key;
}
QString value() const
{
return m_value;
}
private:
QDateTime m_key;
QString m_value;
};
/*
* This costructor is meant to be used by all the classes that needs to write/get a file from the cache.
* All the options are read from the configuration file later.
*/
AkabeiCache::AkabeiCache(QString cachedir)
: m_cachedir(cachedir)
, m_plainEntryList(m_cachedir.entryList())
{
init();
m_keepPackages.append(Config::instance()->keepPackages());
}
/*
* This constructor is meant to be used by the akabei-clean-cache tool, where the options
* can be specified from the command line.
*/
AkabeiCache::AkabeiCache(QString cachedir, QStringList& keepPackages, QHash< QString, bool >& operations)
: m_cachedir(cachedir)
, m_plainEntryList(m_cachedir.entryList())
, m_keepPackages(keepPackages)
{
init();
/*
* Decides the operation to execute
* (more than one can be executed in this same invocation, except for CleanAll of course)
*/
if (operations["clean-all"]) {
cleanAll();
} else {
if (operations["clean-old"]) {
cleanOld();
}
if (operations["clean-unused"]) {
cleanUnused();
}
}
}
void AkabeiCache::init()
{
/*
* Create a new list of cache entries with their full path.
*/
foreach (const QString& cacheFile, m_plainEntryList) {
if (!cacheFile.startsWith(".")) {
m_cacheFiles.append(m_cachedir.absoluteFilePath(cacheFile));
}
}
/*
* Find the date exactly 2 weeks earlier than the current day.
* This is useful later because the most "old" or "unused" packages are actually removed
* only if they are older (or have not been accessed for more than) 2 weeks, in order to prevent
* quite recent files to be removed.
*/
QDateTime current = QDateTime::currentDateTime();
m_weeks2older = current.addDays(-14);
}
/*
* Get the path associated with the filename requested,
* or an empty string if the filename isn't in the cache.
*/
QString AkabeiCache::getPackagePath(QString filename)
{
if (m_plainEntryList.contains(filename)) {
/*
* This fake read has the task of updating the lastRead date we
* use later to see which packages are the least requested of the cache.
*/
QFile filedummy(filename);
filedummy.open(QIODevice::ReadOnly);
filedummy.readLine();
filedummy.close();
return m_cachedir.absoluteFilePath(filename);
}
return "";
}
/*
* Writes a new file on cache
* checking if the maximum size has been reached and acting appropriately if so.
*/
bool AkabeiCache::writePackage(QString& path)
{
if (!QFile::copy(path, m_cachedir.absoluteFilePath(path))) {
m_error = "Could not copy \"" + path + "\" to cache.\n";
return false;
}
/* Convert max size in bytes */
qint64 maxsize = (Config::instance()->maxCacheSize()) * 1024 * 1024;
if (maxsize > 0 && totalCacheSize() >= maxsize) {
/* No policy specified in config */
if (Config::instance()->cachePolicies().isEmpty()) {
return cleanAll();
}
foreach (CacheMaxSizePolicy policy, Config::instance()->cachePolicies()) {
switch (policy) {
case CleanAll:
{
return cleanAll(); /* no need to go on after this... */
}
case CleanOld:
{
if (!cleanOld()) {
return false;
}
break;
}
case CleanUnused:
{
if (!cleanUnused()) {
return false;
}
break;
}
}
}
}
return true;
}
bool AkabeiCache::cleanOld()
{
return cleanConditional(true);
}
bool AkabeiCache::cleanUnused()
{
return cleanConditional(false);
}
/*
* The bool parameter can be extended to support more kind of "comparisons" between packages
* Right now, it just says if the creation date (for CleanOld) or the last read date (for CleanUnused)
* is to be considered as a key to the comparison.
*/
bool AkabeiCache::cleanConditional(bool creationDate)
{
int newCacheSize = (int)(m_cacheFiles.size() / 2); /* each steps halves the cache size */
QList<CachePair> timedFiles;
foreach (const QString& file, m_cacheFiles) {
QFileInfo fileInfo(file);
CachePair pair((creationDate) ? fileInfo.created() : fileInfo.lastRead(), file);
timedFiles.append(pair);
}
qSort(timedFiles);
/*
* Takes only the oldest/least accessed half from the list
*/
foreach (CachePair pair, timedFiles.mid(0, newCacheSize)) {
if (pair.key() >= m_weeks2older || m_keepPackages.contains(m_cachedir.relativeFilePath(pair.value()))) {
continue;
}
QFile file(pair.value());
if (!file.remove()) {
m_error = file.errorString();
return false;
}
}
return true;
}
bool AkabeiCache::cleanAll()
{
foreach (const QString& file, m_plainEntryList) {
QString packageName = getPackageName(file);
qDebug() << packageName;
if (file.startsWith(".") || m_keepPackages.contains(packageName)) {
continue;
}
QFile f(m_cachedir.absoluteFilePath(file));
if (!f.remove()) {
m_error = f.errorString();
return false;
}
}
return true;
}
/*
* The following two functions separate the filename of a package, in the form
* package-version, into name and version.
*/
QString AkabeiCache::getPackageName(const QString& filename)
{
QString pkgname = "";
foreach (const QString& token, filename.split("-")) {
if (token.isEmpty() || token[0].isNumber()) {
break;
}
pkgname += token;
pkgname += "-";
}
pkgname.resize(pkgname.size()-1);
return pkgname;
}
QString AkabeiCache::getPackageVersion(const QString& filename)
{
QString pkgver = "";
foreach (const QString& token, filename.split("-")) {
if (token.isEmpty() || !token[0].isNumber()) {
continue;
}
pkgver += token;
pkgver += "-";
}
pkgver.resize(pkgver.size()-1);
return pkgver;
}
qint64 AkabeiCache::totalCacheSize()
{
qint64 total = 0;
foreach (const QString& filename, m_cacheFiles) {
QFile file(filename);
file.open(QIODevice::ReadOnly);
total += file.size();
file.close();
}
return total;
}
QString AkabeiCache::errorString()
{
return m_error;
}
}
\ No newline at end of file
/*
* This file is part of the Chakra project
* Header file for the cache manager
Copyright (C) 2010 Lisa Vitolo <shainer@chakra-project.org>
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 AKABEICACHE_H
#define AKABEICACHE_H
#include <QStringList>
#include <QHash>
#include <QDir>
#include <QDateTime>
#include <QDebug>
#include "akabeibackend.h"
/*
* This enum describes what to do when the cache reaches the maximum size allowed by the user
*/
namespace Akabei
{
enum CacheMaxSizePolicy
{
CleanAll, CleanOld, CleanUnused
};
class AkabeiCache
{
public:
/**
* Constructs a cache manager and executes the requested operations.
*
* @param cachedir The cache directory.
* @param keepPackages A list of packages the cache must always maintain.
* @param operations A list of operations to perform.
*/
AkabeiCache(QString cachedir, QStringList & keepPackages, QHash<QString, bool> & operations);
/**
* Constructs a cache manager.
*
* @param cachedir The cache directory
*/
AkabeiCache(QString);
/**
* Find a file in the cache.
*
* @param filename filename.
* @returns the file path, or an empty string if the file isn't found.
*/
QString getPackagePath(QString filename);
/**
* Copy a new file in cache.
*
* @param filename the file path.
* @returns true if successful, false otherwise.
*/
bool writePackage(QString &path);
/**
* Error message.
*
* @returns a description if some error occurred, an empty string otherwise.
*/
QString errorString();
private:
QDir m_cachedir;
QStringList m_cacheFiles;
QStringList m_plainEntryList;
QStringList m_keepPackages;
QDateTime m_weeks2older;
QString m_error;
void init();
bool cleanOld();
bool cleanUnused();
bool cleanAll();
QString getPackageName(const QString &);
QString getPackageVersion(const QString &);
qint64 totalCacheSize();
bool cleanConditional(bool);
};
}
#endif
\ No newline at end of file
......@@ -24,6 +24,7 @@ class Config::Private
public:
Private()
: root('/')
, maxCacheSize(0)
{}
~Private() {}
......@@ -36,6 +37,11 @@ class Config::Private
QStringList databases;
QStringList noUpgradeFiles;
QStringList noExtractFiles;
QStringList keepPackages;
int maxCacheSize;
QList<CacheMaxSizePolicy> policies;
QString configFile;
};
......@@ -120,6 +126,21 @@ QStringList Akabei::Config::noExtract() const
return d->noExtractFiles;
}
QStringList Config::keepPackages() const
{
return d->keepPackages;
}
int Config::maxCacheSize() const
{
return d->maxCacheSize;
}
QList<CacheMaxSizePolicy> Config::cachePolicies() const
{
return d->policies;
}
void Config::setCachePath(const QString& path)
{
d->cachePath = path;
......@@ -158,5 +179,32 @@ void Config::setNoExtract(const QStringList& files)
d->noExtractFiles = files;
}
void Config::setKeepPackages(const QStringList& pkgs)
{
d->keepPackages = pkgs;
}
void Config::setMaxCacheSize(int size)
{
d->maxCacheSize = size;
}
void Config::setCachePolicies(const QStringList& policies)
{
if (policies.isEmpty()) {
d->policies.append(CleanAll);
}
foreach (const QString& policy, policies) {
if (policy == "CleanAll") {
d->policies.append(CleanAll);
} else if (policy == "CleanOld") {
d->policies.append(CleanOld);
} else if (policy == "CleanUnused") {
d->policies.append(CleanUnused);
}
}
}
}
......@@ -12,6 +12,7 @@
#define AKABEI_AKABEICONFIG_H
#include <akabeicore_global.h>
#include <akabeicache.h>
class QString;
class QStringList;
......@@ -105,6 +106,33 @@ class AKABEICORESHARED_EXPORT Config
* For directories, applies to all the subtree
*/
void setNoExtract(const QStringList& files);
/**
* @returns the list of packages always to be mantained in our cache
*/
QStringList keepPackages() const;
/**
* Sets the packages to be mantained in cache
*/
void setKeepPackages(const QStringList& pkgs);
/**
* @returns the max cache size in MB
*/
int maxCacheSize() const;
/**
* Sets the maximum size allowed for the cache
*/
void setMaxCacheSize(int size);
/**
* @returns a list of policies to be applied when the maximum size is reached
*/
QList<CacheMaxSizePolicy> cachePolicies() const;
/**
* Sets the policies to be applied to the cache
*/
void setCachePolicies(const QStringList& policies);
private:
Config();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment