commit 3bfe61ce7b366b43f7ec9c2e7b84422543209364
parent 56f0dbd30c4811dd4bf2abfb0b093cae72cddb25
Author: cfillion <cfillion@users.noreply.github.com>
Date: Wed, 20 Jan 2016 15:10:48 -0500
handle every file conflicts cases the same way
Diffstat:
8 files changed, 109 insertions(+), 42 deletions(-)
diff --git a/src/database.cpp b/src/database.cpp
@@ -85,6 +85,11 @@ int Database::version() const
return version;
}
+int Database::errorCode() const
+{
+ return sqlite3_extended_errcode(m_db);
+}
+
void Database::begin()
{
// EXCLUSIVE -> don't wait until the first query to aquire a lock
diff --git a/src/database.hpp b/src/database.hpp
@@ -39,6 +39,7 @@ public:
void exec(const char *sql);
uint64_t lastInsertId() const;
int version() const;
+ int errorCode() const;
void begin();
void commit();
diff --git a/src/registry.cpp b/src/registry.cpp
@@ -24,6 +24,7 @@
#include "remote.hpp"
#include <reaper_plugin_functions.h>
+#include <sqlite3.h>
using namespace std;
@@ -57,6 +58,10 @@ Registry::Registry(const Path &path)
);
m_forgetFiles = m_db.prepare("DELETE FROM files WHERE entry = ?");
+ m_savepoint = m_db.prepare("SAVEPOINT savept");
+ m_release = m_db.prepare("RELEASE SAVEPOINT savept");
+ m_restore = m_db.prepare("ROLLBACK TO SAVEPOINT savept");
+
// lock the database
m_db.begin();
}
@@ -98,8 +103,11 @@ void Registry::migrate()
m_db.commit();
}
-void Registry::push(Version *ver)
+void Registry::push(Version *ver, vector<Path> *conflicts)
{
+ m_savepoint->exec();
+ bool hasConflicts = false;
+
Package *pkg = ver->package();
Category *cat = pkg->category();
RemoteIndex *ri = cat->index();
@@ -120,8 +128,25 @@ void Registry::push(Version *ver)
for(const Path &path : ver->files()) {
m_insertFile->bind(1, entryId);
m_insertFile->bind(2, path.join('/'));
- m_insertFile->exec();
+ try {
+ m_insertFile->exec();
+ }
+ catch(const reapack_error &) {
+ if(conflicts && m_db.errorCode() == SQLITE_CONSTRAINT_UNIQUE) {
+ hasConflicts = true;
+ conflicts->push_back(path);
+ }
+ else {
+ m_restore->exec();
+ throw;
+ }
+ }
}
+
+ if(hasConflicts)
+ m_restore->exec();
+ else
+ m_release->exec();
}
Registry::Entry Registry::query(Package *pkg) const
diff --git a/src/registry.hpp b/src/registry.hpp
@@ -49,7 +49,7 @@ public:
Entry query(Package *) const;
std::vector<Entry> queryAll(const Remote &) const;
std::set<Path> getFiles(const Entry &) const;
- void push(Version *);
+ void push(Version *, std::vector<Path> *conflicts = nullptr);
void forget(const Entry &);
void commit();
@@ -68,6 +68,10 @@ private:
Statement *m_insertFile;
Statement *m_clearFiles;
Statement *m_forgetFiles;
+
+ Statement *m_savepoint;
+ Statement *m_release;
+ Statement *m_restore;
};
#endif
diff --git a/src/transaction.cpp b/src/transaction.cpp
@@ -30,14 +30,14 @@
using namespace std;
Transaction::Transaction(const Path &root)
- : m_root(root), m_isCancelled(false), m_hasConflicts(false)
+ : m_root(root), m_isCancelled(false)
{
m_dbPath = m_root + "ReaPack";
m_registry = new Registry(m_dbPath + "registry.db");
m_downloadQueue.onDone([=](void *) {
- if(m_installQueue.empty() || m_hasConflicts)
+ if(m_installQueue.empty())
finish();
else
install();
@@ -72,31 +72,53 @@ void Transaction::upgradeAll(Download *dl)
if(!saveFile(dl, path))
return;
+ RemoteIndex *ri;
+
try {
- RemoteIndex *ri = RemoteIndex::load(dl->name(), path.join().c_str());
+ ri = RemoteIndex::load(dl->name(), path.join().c_str());
m_remoteIndexes.push_back(ri);
+ }
+ catch(const reapack_error &e) {
+ addError(e.what(), dl->url());
+ return;
+ }
- for(Package *pkg : ri->packages()) {
- Registry::Entry entry = m_registry->query(pkg);
-
- Version *ver = pkg->lastVersion();
+ for(Package *pkg : ri->packages())
+ upgrade(pkg);
+}
- set<Path> files = ver->files();
- registerFiles(files);
+void Transaction::upgrade(Package *pkg)
+{
+ Version *ver = pkg->lastVersion();
+ Registry::Entry entry = m_registry->query(pkg);
- if(entry.status == Registry::UpToDate) {
- if(allFilesExists(files))
- continue;
- else
- entry.status = Registry::Uninstalled;
+ try {
+ vector<Path> conflicts;
+ m_registry->push(ver, &conflicts);
+
+ if(!conflicts.empty()) {
+ for(const Path &path : conflicts) {
+ addError("Conflict: " + path.join() +
+ " is already owned by another package",
+ ver->fullName());
}
- m_installQueue.push({ver, entry});
+ return;
}
}
catch(const reapack_error &e) {
- addError(e.what(), dl->url());
+ addError(e.what(), ver->fullName());
+ return;
}
+
+ if(entry.status == Registry::UpToDate) {
+ if(allFilesExists(ver->files()))
+ return;
+ else
+ entry.status = Registry::Uninstalled;
+ }
+
+ m_installQueue.push({ver, entry});
}
void Transaction::install()
@@ -117,8 +139,6 @@ void Transaction::install()
else
m_new.push_back(entry);
- m_registry->push(ver);
-
const set<Path> &removedFiles = task->removedFiles();
m_removals.insert(removedFiles.begin(), removedFiles.end());
@@ -236,21 +256,6 @@ bool Transaction::allFilesExists(const set<Path> &list) const
return true;
}
-void Transaction::registerFiles(const set<Path> &list)
-{
- for(const Path &path : list) {
- if(!m_files.count(path))
- continue;
-
- addError("Conflict: This file is owned by more than one package",
- path.join());
-
- m_hasConflicts = true;
- }
-
- m_files.insert(list.begin(), list.end());
-}
-
void Transaction::addTask(Task *task)
{
m_tasks.push_back(task);
diff --git a/src/transaction.hpp b/src/transaction.hpp
@@ -75,8 +75,8 @@ private:
void finish();
void upgradeAll(Download *);
+ void upgrade(Package *pkg);
bool allFilesExists(const std::set<Path> &) const;
- void registerFiles(const std::set<Path> &);
void addTask(Task *);
Registry *m_registry;
@@ -95,8 +95,6 @@ private:
std::vector<Task *> m_tasks;
std::queue<Task *> m_taskQueue;
- std::set<Path> m_files;
- bool m_hasConflicts;
Signal m_onFinish;
Signal m_onDestroy;
diff --git a/test/database.cpp b/test/database.cpp
@@ -4,6 +4,8 @@
#include <errors.hpp>
+#include <sqlite3.h>
+
using namespace std;
static const char *M = "[database]";
@@ -160,3 +162,16 @@ TEST_CASE("bind temporary strings", M) {
REQUIRE(got == "hello");
}
+
+TEST_CASE("sqlite error code", M) {
+ Database db;
+ db.exec("CREATE TABLE a(b INTEGER UNIQUE); INSERT INTO a VALUES(1)");
+ REQUIRE(db.errorCode() == SQLITE_OK);
+
+ try {
+ db.exec("INSERT INTO a VALUES(1)");
+ }
+ catch(const reapack_error &) {}
+
+ REQUIRE(db.errorCode() == SQLITE_CONSTRAINT_UNIQUE);
+}
diff --git a/test/registry.cpp b/test/registry.cpp
@@ -108,7 +108,7 @@ TEST_CASE("forget registry entry", M) {
REQUIRE(afterForget.version == 0);
}
-TEST_CASE("enforce unique files", M) {
+TEST_CASE("file conflicts", M) {
Registry reg;
{
@@ -120,13 +120,27 @@ TEST_CASE("enforce unique files", M) {
Category cat("Hello", &ri);
Package pkg(Package::ScriptType, "Duplicate Package", &cat);
Version *ver = new Version("1.0", &pkg);
- Source *src = new Source(Source::GenericPlatform, "file", "url", ver);
- ver->addSource(src);
+ Source *src1 = new Source(Source::GenericPlatform, "file", "url", ver);
+ Source *src2 = new Source(Source::GenericPlatform, "file2", "url", ver);
+ ver->addSource(src1);
+ ver->addSource(src2);
pkg.addVersion(ver);
+ CHECK(reg.query(&pkg).status == Registry::Uninstalled);
+
try {
reg.push(ver);
FAIL("duplicate was accepted");
}
catch(const reapack_error &) {}
+
+ REQUIRE(reg.query(&pkg).status == Registry::Uninstalled);
+
+ vector<Path> conflicts;
+ reg.push(ver, &conflicts);
+
+ REQUIRE(conflicts.size() == 1);
+ REQUIRE(conflicts[0] == src1->targetPath());
+
+ REQUIRE(reg.query(&pkg).status == Registry::Uninstalled);
}