Commit 4721e8fd authored by Dario Freddi's avatar Dario Freddi

Move on. We also need decent version handling

Signed-off-by: default avatarDario Freddi <drf@kde.org>
parent a07e9f86
......@@ -9,7 +9,7 @@ akabeibackend.cpp
akabeiconfig.cpp
akabeidatabase.cpp
akabeigroup.cpp
akabeilibarchivehelper.cpp
akabeihelpers_p.cpp
akabeioperation.cpp
akabeioperationrunner.cpp
akabeipackage.cpp
......
......@@ -12,7 +12,7 @@
Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
*/
#include "akabeilibarchivehelper.h"
#include "akabeihelpers_p.h"
#include "akabeiconfig.h"
......@@ -21,6 +21,8 @@
#include <archive.h>
#include <archive_entry.h>
#include <stdlib.h>
namespace Akabei
{
......@@ -372,4 +374,176 @@ int extract_single_file(struct archive *archive, struct archive_entry *entry)
return(errors);
}
/** Compare two version strings and determine which one is 'newer'.
* Returns a value comparable to the way strcmp works. Returns 1
* if a is newer than b, 0 if a and b are the same version, or -1
* if b is newer than a.
*
* This function has been adopted from libalpm, which reports:
* This function has been adopted from the rpmvercmp function located
* at lib/rpmvercmp.c, and was most recently updated against rpm
* version 4.4.2.3. Small modifications have been made to make it more
* consistent with the libalpm coding style.
*
* Keep in mind that the pkgrel is only compared if it is available
* on both versions handed to this function. For example, comparing
* 1.5-1 and 1.5 will yield 0; comparing 1.5-1 and 1.5-2 will yield
* -1 as expected. This is mainly for supporting versioned dependencies
* that do not include the pkgrel.
*/
int compare_versions(const char *a, const char *b)
{
char oldch1, oldch2;
char *str1, *str2;
char *ptr1, *ptr2;
char *one, *two;
int rc;
int isnum;
int ret = 0;
/* libalpm added code. ensure our strings are not null */
if(!a) {
if(!b) return(0);
return(-1);
}
if(!b) return(1);
/* easy comparison to see if versions are identical */
if(strcmp(a, b) == 0) return(0);
str1 = strdup(a);
str2 = strdup(b);
one = str1;
two = str2;
/* loop through each version segment of str1 and str2 and compare them */
while(*one && *two) {
while(*one && !isalnum((int)*one)) one++;
while(*two && !isalnum((int)*two)) two++;
/* If we ran to the end of either, we are finished with the loop */
if(!(*one && *two)) break;
ptr1 = one;
ptr2 = two;
/* grab first completely alpha or completely numeric segment */
/* leave one and two pointing to the start of the alpha or numeric */
/* segment and walk ptr1 and ptr2 to end of segment */
if(isdigit((int)*ptr1)) {
while(*ptr1 && isdigit((int)*ptr1)) ptr1++;
while(*ptr2 && isdigit((int)*ptr2)) ptr2++;
isnum = 1;
} else {
while(*ptr1 && isalpha((int)*ptr1)) ptr1++;
while(*ptr2 && isalpha((int)*ptr2)) ptr2++;
isnum = 0;
}
/* save character at the end of the alpha or numeric segment */
/* so that they can be restored after the comparison */
oldch1 = *ptr1;
*ptr1 = '\0';
oldch2 = *ptr2;
*ptr2 = '\0';
/* this cannot happen, as we previously tested to make sure that */
/* the first string has a non-null segment */
if (one == ptr1) {
ret = -1; /* arbitrary */
goto cleanup;
}
/* take care of the case where the two version segments are */
/* different types: one numeric, the other alpha (i.e. empty) */
/* numeric segments are always newer than alpha segments */
/* XXX See patch #60884 (and details) from bugzilla #50977. */
if (two == ptr2) {
ret = isnum ? 1 : -1;
goto cleanup;
}
if (isnum) {
/* this used to be done by converting the digit segments */
/* to ints using atoi() - it's changed because long */
/* digit segments can overflow an int - this should fix that. */
/* throw away any leading zeros - it's a number, right? */
while (*one == '0') one++;
while (*two == '0') two++;
/* whichever number has more digits wins */
if (strlen(one) > strlen(two)) {
ret = 1;
goto cleanup;
}
if (strlen(two) > strlen(one)) {
ret = -1;
goto cleanup;
}
}
/* strcmp will return which one is greater - even if the two */
/* segments are alpha or if they are numeric. don't return */
/* if they are equal because there might be more segments to */
/* compare */
rc = strcmp(one, two);
if (rc) {
ret = rc < 1 ? -1 : 1;
goto cleanup;
}
/* restore character that was replaced by null above */
*ptr1 = oldch1;
one = ptr1;
*ptr2 = oldch2;
two = ptr2;
/* libalpm added code. check if version strings have hit the pkgrel
* portion. depending on which strings have hit, take correct action.
* this is all based on the premise that we only have one dash in
* the version string, and it separates pkgver from pkgrel. */
if(*ptr1 == '-' && *ptr2 == '-') {
/* no-op, continue comparing since we are equivalent throughout */
} else if(*ptr1 == '-') {
/* ptr1 has hit the pkgrel and ptr2 has not. continue version
* comparison after stripping the pkgrel from ptr1. */
*ptr1 = '\0';
} else if(*ptr2 == '-') {
/* ptr2 has hit the pkgrel and ptr1 has not. continue version
* comparison after stripping the pkgrel from ptr2. */
*ptr2 = '\0';
}
}
/* this catches the case where all numeric and alpha segments have */
/* compared identically but the segment separating characters were */
/* different */
if ((!*one) && (!*two)) {
ret = 0;
goto cleanup;
}
/* the final showdown. we never want a remaining alpha string to
* beat an empty string. the logic is a bit weird, but:
* - if one is empty and two is not an alpha, two is newer.
* - if one is an alpha, two is newer.
* - otherwise one is newer.
* */
if ( ( !*one && !isalpha((int)*two) )
|| isalpha((int)*one) ) {
ret = -1;
} else {
ret = 1;
}
cleanup:
free(str1);
free(str2);
return(ret);
}
}
#include "akabeihelpers_p.moc"
/* This file is part of the Chakra project
Copyright (C) 2010 Dario Freddi <drf@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 AKABEI_HELPERS_P_H
#define AKABEI_HELPERS_P_H
#include <QtCore/QEventLoop>
#include <QtCore/QUuid>
namespace Akabei {
class Package;
namespace Helpers
{
static int extract_single_file(struct archive *archive, struct archive_entry *entry);
static int compare_versions(const char *a, const char *b);
class PackageEventLoop : public QEventLoop
{
Q_OBJECT
public:
PackageEventLoop(QObject *parent = 0) : QEventLoop(parent) {}
virtual ~PackageEventLoop() {}
void setUuid(const QUuid &uuid) {
m_uuid = uuid;
}
QList<Akabei::Package*> result() const {
return m_result;
}
public slots:
void requestQuit(const QUuid &uuid, const QList<Akabei::Package*> &result) {
if (uuid == m_uuid) {
m_result = result;
quit();
}
}
private:
QUuid m_uuid;
QList<Akabei::Package*> m_result;
};
}
}
#endif // AKABEI_HELPERS_P_H
/* This file is part of the Chakra project
Copyright (C) 2010 Dario Freddi <drf@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 AKABEI_AKABEILIBARCHIVEHELPER_H
#define AKABEI_AKABEILIBARCHIVEHELPER_H
namespace Akabei {
namespace LibArchiveHelper
{
static int extract_single_file(struct archive *archive, struct archive_entry *entry);
};
}
#endif // AKABEI_AKABEILIBARCHIVEHELPER_H
......@@ -9,6 +9,7 @@
*/
#include "akabeioperation_p.h"
#include <qeventloop.h>
namespace Akabei {
......@@ -197,4 +198,13 @@ void Operation::setValidationFinished(bool result)
}
}
void Operation::concurrentValidate()
{
Q_D(Operation);
d->validateLoop = new QEventLoop;
validate();
d->validateLoop.data()->exec();
d->validateLoop.data()->deleteLater();
}
}
......@@ -69,6 +69,7 @@ public:
List subOperations() const;
//OperationRunner *runner();
void concurrentValidate();
protected:
explicit Operation(const QString &targetName);
......
......@@ -12,7 +12,9 @@
#define AKABEIOPERATION_P_H
#include "akabeioperation.h"
#include <QWeakPointer>
class QEventLoop;
namespace Akabei {
class OperationPrivate {
......@@ -38,6 +40,8 @@ public:
Operation::Status status;
Error *error;
Operation::List children;
QWeakPointer<QEventLoop> validateLoop;
};
}
......
......@@ -18,6 +18,8 @@ bool priorityLessThan(Operation *o1, Operation *o2)
return o1->priority() < o2->priority();
}
/////////
OperationRunner::OperationRunner(QObject *parent)
: QObject(parent)
, d_ptr(new OperationRunnerPrivate)
......@@ -82,7 +84,9 @@ void OperationRunner::start()
qSort(i.value().begin(), i.value().end(), priorityLessThan);
}
// Now start handling the various phases in a separate thread
// Now start validating all the phases
d->phase = Operation::Phase1;
d->validateNextPhase();
}
void OperationRunner::cancel()
......
......@@ -25,6 +25,11 @@ public:
~OperationRunnerPrivate() {}
QHash<Operation::Phase, QList< Operation* > > operations;
void validateNextPhase();
void processNextPhase();
Operation::Phase phase;
};
}
......
......@@ -14,9 +14,21 @@
#include "akabeiconfig.h"
#include <QFile>
#include <QtConcurrentMap>
#include <qeventloop.h>
#include <QtCore/QFutureWatcher>
#include "akabeibackend.h"
#include "akabeidatabase.h"
#include "akabeihelpers_p.h"
#include "akabeipackage.h"
using namespace Akabei;
QString queryFromName(const QString &name)
{
}
ValidatorThread::ValidatorThread(QObject* parent)
: QThread(parent)
{
......@@ -39,96 +51,152 @@ void ValidatorThread::run()
processNextPhase();
}
void ValidatorThread::validateOperation(Operation* op)
QList<Operation*> ValidatorThread::joinOperations(Operation* op, bool shouldBeReady)
{
QList<Operation*> retlist;
if (op->status() != Operation::StatusReady) {
// TODO: This might be async as well. Handle this
op->validate();
if (op->status() != Operation::StatusReady) {
//Problems.
// TODO take action
}
retlist << op;
}
// Process children
foreach (Operation *child, op->subOperations()) {
validateOperation(child);
retlist << joinOperations(child, shouldBeReady);
}
return;
}
void ValidatorThread::processNextPhase()
{
// First of all validate everything and compute dependencies. Recurse over one's self
// until all dependencies are satisfied.
QList<Operation*> allOps;
QList<Operation*> processOps;
foreach (Operation *op, m_operations[m_currentPhase]) {
allOps << joinOperations(op, false);
processOps << joinOperations(op, true);
}
if (processOps.isEmpty()) {
// We can advance to the next phase, if any
return;
}
// Start the concurrent validation
QEventLoop e;
QFutureWatcher< void > watcher(this);
connect(&watcher, SIGNAL(finished()), &e, SLOT(quit()));
connect(&watcher, SIGNAL(progressValueChanged(int)), this, SLOT(streamValidationProgress(int)));
QFuture< void > future = QtConcurrent::map(processOps, &Operation::concurrentValidate);
watcher.setFuture(future);
e.exec();
// Now populate target names and dependencies
QStringList processTargetNames;
QStringList targetNames;
QStringList dependTargets;
foreach (Operation *op, m_operations[m_currentPhase]) {
validateOperation(op);
foreach (Operation *op, processOps) {
processTargetNames << op->targetName();
dependTargets << op->targetDependencies();
}
foreach (Operation *op, allOps) {
targetNames << op->targetName();
}
// Check dependencies
dependTargets.removeDuplicates();
QStringList missingDeps;
Package *missingDeps;
QStringList unresolvableDeps;
foreach (const QString &dep, dependTargets) {
// TODO: Check local database here, and add dependencies
// Is it in the current operation?
if (targetNames.contains(dep)) {
// Cool, pass by
continue;
}
// Is it in the local database?
if (Backend::instance()->localDatabase()->queryPackages(queryFromName(dep)).size() > 0) {
// TODO: eventually check for version
// Cool, pass by
continue;
}
// The dependency is hereby missing. Is it resolvable? Let's see.
Helpers::PackageEventLoop loop;
connect(Backend::instance(), SIGNAL(queryPackagesCompleted(QUuid,QList<Akabei::Package*>)),
&loop, SLOT(requestQuit(QUuid,QList<Akabei::Package*>)));
loop.setUuid(Backend::instance()->queryPackages(queryFromName(dep)));
loop.exec();
if (loop.result().size() > 0) {
// TODO: Check for version here.
// Ok, we just need to install it. Let's add the first target
// TODO: Better heuristics in case of multiple targets retrieved
missingDeps << loop.result().first();
} else {
// The dependency is not satisfiable
unresolvableDeps << dep;
}
}
if (!unresolvableDeps.isEmpty()) {
// failure.
return;
}
if (!missingDeps.isEmpty()) {
// TODO Act upon missing dependencies, add them to the stack
// And recurse
// Do a early recurse
// TODO: Add missing targets here.
processNextPhase();
return;
}
// Ok, now check validity of stuff. Start by populating some lists
// Ok, now check validity of stuff.
QStringList filesystemAdditions;
QStringList conflictTargets;
foreach (Operation *op, m_operations[m_currentPhase]) {
QStringList targetRemovals;
foreach (Operation *op, allOps) {
filesystemAdditions << op->fileSystemAdditions();
conflictTargets << op->conflictingTargets();
targetRemovals << op->targetRemovals();
// Check filesystem conflicts
foreach (const QString &target, op->fileSystemAdditions()) {
if (QFile::exists(Config::instance()->root() + target)) {
// Problem, FS conflict.
// TODO handle
}
}
foreach (const QString &target, op->conflictingTargets()) {
if (targetNames.contains(target)) {
// Problem, attempting to install a package conflicting with something in the transaction.
// TODO handle
}
// Now check the local database
if (Backend::instance()->localDatabase()->queryPackages(queryFromName(target)).size() > 0) {
// Problem, trying to install a package conflicting with an installed package
// TODO handle
}
}
}
// Check for internal conflicts, so easy thanks to the power of QStringList::removeDuplicates.
int dupCount = filesystemAdditions.removeDuplicates();
if (dupCount > 0) {
// Internal conflicts
//TODO handle
}
// Check filesystem itself now
foreach (const QString &target, filesystemAdditions) {
if (QFile::exists(Config::instance()->root() + target)) {
// Problem, FS conflict.
// TODO handle
// Check if we are breaking dependencies somehow
foreach (const QString &target, op->targetRemovals()) {
QString sql = "SELECT * FROM packages WHERE Depends LIKE \"%u;" + target + "%u\"";
QList<Package*> ret = Backend::instance()->localDatabase()->queryPackages(sql);
// Iterate: we might have found targets that are not the ones we're looking for (due to version handling)
foreach (Package *p, ret) {
// Check also that the package itself is not being removed!
if (p->name() == target) {
// Problem, attempting to remove a package which some installed packages depend on.
}
}
}
// Now check conflicts and act upon them.
foreach (const QString &target, conflictTargets) {
if (targetNames.contains(target)) {
// Problem, attempting to install a package conflicting with itself.
// TODO handle
}
// TODO check the local database
}
// Good. The transaction appears to be fully valid. Now run everything.
// TODO handle concurrency
foreach (Operation *op, m_operations[m_currentPhase]) {
// TODO: This might be async as well. Handle this
op->validate();
if (op->status() != Operation::StatusPerformed) {
//Problems.
// TODO take action
}
}
// Good. Increase the phase and recurse (eventually)
m_currentPhase = (Operation::Phase)((int)m_currentPhase + 1);
if (m_currentPhase > 5) {
// TODO do not recurse here
}
processNextPhase();
// Good. The phase appears to be fully valid.
}
......@@ -32,13 +32,15 @@ class ValidatorThread : public QThread
private:
void processNextPhase();
void validateOperation(Operation *op);
void joinOperations(Operation *op);
private:
QHash<Operation::Phase, QList< Operation* > > m_operations;
Operation::Phase m_currentPhase;
friend class OperationRunner;
public slots:
void streamValidationProgress(int);
};
}
......
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