nextcloud-desktop/src/mirall/syncjournaldb.cpp

1076 lines
34 KiB
C++
Raw Normal View History

/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* 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; version 2 of the License.
*
* This program 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 General Public License
* for more details.
*/
#include <QFile>
#include <QStringList>
#include <QDebug>
#include "mirall/ownsql.h"
#include <inttypes.h>
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
#include "utility.h"
#include "version.h"
#include "../../csync/src/std/c_jhash.h"
namespace Mirall {
SyncJournalDb::SyncJournalDb(const QString& path, QObject *parent) :
QObject(parent), _transaction(0), _possibleUpgradeFromMirall_1_5(false)
{
_dbFile = path;
if( !_dbFile.endsWith('/') ) {
_dbFile.append('/');
}
_dbFile.append(".csync_journal.db");
2013-11-13 14:16:00 +04:00
}
bool SyncJournalDb::exists()
{
QMutexLocker locker(&_mutex);
return (!_dbFile.isEmpty() && QFile::exists(_dbFile));
}
2014-10-13 16:14:43 +04:00
QString SyncJournalDb::databaseFilePath()
{
return _dbFile;
}
void SyncJournalDb::startTransaction()
{
if( _transaction == 0 ) {
if( !_db.transaction() ) {
qDebug() << "ERROR starting transaction: " << _db.error();
return;
}
_transaction = 1;
2014-10-17 13:35:06 +04:00
// qDebug() << "XXX Transaction start!";
} else {
qDebug() << "Database Transaction is running, do not starting another one!";
}
}
void SyncJournalDb::commitTransaction()
{
if( _transaction == 1 ) {
if( ! _db.commit() ) {
qDebug() << "ERROR committing to the database: " << _db.error();
return;
}
_transaction = 0;
2014-10-17 13:35:06 +04:00
// qDebug() << "XXX Transaction END!";
} else {
qDebug() << "No database Transaction to commit";
}
}
bool SyncJournalDb::sqlFail( const QString& log, const SqlQuery& query )
{
commitTransaction();
qWarning() << "SQL Error" << log << query.error();
Q_ASSERT(!"SQL ERROR");
_db.close();
return false;
}
bool SyncJournalDb::checkConnect()
{
if( _db.isOpen() ) {
return true;
}
if( _dbFile.isEmpty() || !QFile::exists(_dbFile) ) {
qDebug() << "Database " + _dbFile + " is empty or does not exist";
return false;
}
if( !_db.open(_dbFile) ) {
QString error = _db.error();
qDebug() << "Error opening the db: " << error;
return false;
}
SqlQuery pragma1(_db);
2014-10-11 19:33:08 +04:00
pragma1.prepare("SELECT sqlite_version();");
if (!pragma1.exec()) {
return sqlFail("SELECT sqlite_version()", pragma1);
} else {
pragma1.next();
qDebug() << "sqlite3 version" << pragma1.stringValue(0);
2014-10-11 19:33:08 +04:00
}
pragma1.prepare("PRAGMA synchronous = 1;");
if (!pragma1.exec()) {
return sqlFail("Set PRAGMA synchronous", pragma1);
}
pragma1.prepare("PRAGMA case_sensitive_like = ON;");
if (!pragma1.exec()) {
return sqlFail("Set PRAGMA case_sensitivity", pragma1);
}
/* Because insert are so slow, e do everything in a transaction, and one need to call commit */
startTransaction();
SqlQuery createQuery(_db);
createQuery.prepare("CREATE TABLE IF NOT EXISTS metadata("
"phash INTEGER(8),"
"pathlen INTEGER,"
"path VARCHAR(4096),"
"inode INTEGER,"
"uid INTEGER,"
"gid INTEGER,"
"mode INTEGER,"
"modtime INTEGER(8),"
"type INTEGER,"
"md5 VARCHAR(32)," /* This is the etag. Called md5 for compatibility */
// updateDatabaseStructure() will add a fileid column
// updateDatabaseStructure() will add a remotePerm column
"PRIMARY KEY(phash)"
");");
if (!createQuery.exec()) {
return sqlFail("Create table metadata", createQuery);
}
createQuery.prepare("CREATE TABLE IF NOT EXISTS downloadinfo("
"path VARCHAR(4096),"
"tmpfile VARCHAR(4096),"
"etag VARCHAR(32),"
"errorcount INTEGER,"
"PRIMARY KEY(path)"
");");
if (!createQuery.exec()) {
return sqlFail("Create table downloadinfo", createQuery);
}
createQuery.prepare("CREATE TABLE IF NOT EXISTS uploadinfo("
"path VARCHAR(4096),"
"chunk INTEGER,"
"transferid INTEGER,"
"errorcount INTEGER,"
"size INTEGER(8),"
"modtime INTEGER(8),"
"PRIMARY KEY(path)"
");");
if (!createQuery.exec()) {
return sqlFail("Create table uploadinfo", createQuery);
2013-11-15 20:42:48 +04:00
}
// create the blacklist table.
createQuery.prepare("CREATE TABLE IF NOT EXISTS blacklist ("
"path VARCHAR(4096),"
"lastTryEtag VARCHAR[32],"
"lastTryModtime INTEGER[8],"
"retrycount INTEGER,"
"errorstring VARCHAR[4096],"
"PRIMARY KEY(path)"
");");
if (!createQuery.exec()) {
return sqlFail("Create table blacklist", createQuery);
}
createQuery.prepare("CREATE TABLE IF NOT EXISTS version("
"major INTEGER(8),"
"minor INTEGER(8),"
"patch INTEGER(8),"
"custom VARCHAR(256)"
");");
if (!createQuery.exec()) {
2014-10-06 20:36:32 +04:00
return sqlFail("Create table version", createQuery);
}
SqlQuery versionQuery("SELECT major, minor FROM version;", _db);
if (!versionQuery.next()) {
// If there was no entry in the table, it means we are likely upgrading from 1.5
_possibleUpgradeFromMirall_1_5 = true;
} else {
// Delete the existing entry so we can replace it by the new one
createQuery.prepare("DELETE FROM version;");
if (!createQuery.exec()) {
return sqlFail("Remove version", createQuery);
}
}
createQuery.prepare("INSERT INTO version (major, minor, patch) VALUES ( ?1, ?2 , ?3 );");
createQuery.bindValue(1, MIRALL_VERSION_MAJOR);
createQuery.bindValue(2, MIRALL_VERSION_MINOR);
createQuery.bindValue(3, MIRALL_VERSION_PATCH);
if (!createQuery.exec()) {
return sqlFail("Insert Version", createQuery);
}
commitInternal("checkConnect");
bool rc = updateDatabaseStructure();
if( !rc ) {
qDebug() << "WARN: Failed to update the database structure!";
}
_getFileRecordQuery.reset(new SqlQuery(_db));
_getFileRecordQuery->prepare("SELECT path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm FROM "
"metadata WHERE phash=?1" );
_setFileRecordQuery.reset(new SqlQuery(_db) );
_setFileRecordQuery->prepare("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12);" );
_getDownloadInfoQuery.reset(new SqlQuery(_db) );
_getDownloadInfoQuery->prepare( "SELECT tmpfile, etag, errorcount FROM "
"downloadinfo WHERE path=?1" );
_setDownloadInfoQuery.reset(new SqlQuery(_db) );
_setDownloadInfoQuery->prepare( "INSERT OR REPLACE INTO downloadinfo "
"(path, tmpfile, etag, errorcount) "
"VALUES ( ?1 , ?2, ?3, ?4 )" );
_deleteDownloadInfoQuery.reset(new SqlQuery(_db) );
_deleteDownloadInfoQuery->prepare( "DELETE FROM downloadinfo WHERE path=?1" );
_getUploadInfoQuery.reset(new SqlQuery(_db));
_getUploadInfoQuery->prepare( "SELECT chunk, transferid, errorcount, size, modtime FROM "
"uploadinfo WHERE path=?1" );
_setUploadInfoQuery.reset(new SqlQuery(_db));
_setUploadInfoQuery->prepare( "INSERT OR REPLACE INTO uploadinfo "
"(path, chunk, transferid, errorcount, size, modtime) "
"VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 )");
_deleteUploadInfoQuery.reset(new SqlQuery(_db));
_deleteUploadInfoQuery->prepare("DELETE FROM uploadinfo WHERE path=?1" );
_deleteFileRecordPhash.reset(new SqlQuery(_db));
_deleteFileRecordPhash->prepare("DELETE FROM metadata WHERE phash=?1");
_deleteFileRecordRecursively.reset(new SqlQuery(_db));
_deleteFileRecordRecursively->prepare("DELETE FROM metadata WHERE path LIKE(?||'/%')");
QString sql( "SELECT lastTryEtag, lastTryModtime, retrycount, errorstring "
"FROM blacklist WHERE path=?1");
if( Utility::fsCasePreserving() ) {
// if the file system is case preserving we have to check the blacklist
// case insensitively
sql += QLatin1String(" COLLATE NOCASE");
}
_blacklistQuery.reset(new SqlQuery(_db));
_blacklistQuery->prepare(sql);
return rc;
}
void SyncJournalDb::close()
{
QMutexLocker locker(&_mutex);
commitTransaction();
_getFileRecordQuery.reset(0);
_setFileRecordQuery.reset(0);
_getDownloadInfoQuery.reset(0);
_setDownloadInfoQuery.reset(0);
_deleteDownloadInfoQuery.reset(0);
_getUploadInfoQuery.reset(0);
_setUploadInfoQuery.reset(0);
_deleteUploadInfoQuery.reset(0);
_deleteFileRecordPhash.reset(0);
_deleteFileRecordRecursively.reset(0);
_blacklistQuery.reset(0);
_possibleUpgradeFromMirall_1_5 = false;
2013-11-20 21:19:14 +04:00
_db.close();
_db = SqlDatabase(); // avoid the warning SqlDatabasePrivate::removeDatabase: connection [...] still in use
_avoidReadFromDbOnNextSyncFilter.clear();
}
bool SyncJournalDb::updateDatabaseStructure()
{
QStringList columns = tableColumns("metadata");
bool re = true;
// check if the file_id column is there and create it if not
if( !checkConnect() ) {
return false;
}
if( columns.indexOf(QLatin1String("fileid")) == -1 ) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN fileid VARCHAR(128);");
if( !query.exec() ) {
sqlFail("updateDatabaseStructure: Add column fileid", query);
re = false;
}
query.prepare("CREATE INDEX metadata_file_id ON metadata(fileid);");
if( ! query.exec() ) {
sqlFail("updateDatabaseStructure: create index fileid", query);
re = false;
}
commitInternal("update database structure: add fileid col");
}
if( columns.indexOf(QLatin1String("remotePerm")) == -1 ) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN remotePerm VARCHAR(128);");
if( !query.exec()) {
sqlFail("updateDatabaseStructure: add column remotePerm", query);
re = false;
}
commitInternal("update database structure (remotePerm");
}
if( 1 ) {
SqlQuery query(_db);
query.prepare("CREATE INDEX IF NOT EXISTS metadata_inode ON metadata(inode);");
if( !query.exec()) {
sqlFail("updateDatabaseStructure: create index inode", query);
re = false;
}
commitInternal("update database structure: add inode index");
}
if( 1 ) {
SqlQuery query(_db);
query.prepare("CREATE INDEX IF NOT EXISTS metadata_pathlen ON metadata(pathlen);");
if( !query.exec()) {
sqlFail("updateDatabaseStructure: create index pathlen", query);
re = false;
}
commitInternal("update database structure: add pathlen index");
}
return re;
}
QStringList SyncJournalDb::tableColumns( const QString& table )
{
QStringList columns;
if( !table.isEmpty() ) {
if( checkConnect() ) {
QString q = QString("PRAGMA table_info('%1');").arg(table);
SqlQuery query(_db);
query.prepare(q);
if(!query.exec()) {
QString err = query.error();
qDebug() << "Error creating prepared statement: " << query.lastQuery() << ", Error:" << err;;
return columns;
}
while( query.next() ) {
columns.append( query.stringValue(1) );
}
}
}
qDebug() << "Columns in the current journal: " << columns;
return columns;
}
2014-10-13 16:14:43 +04:00
qint64 SyncJournalDb::getPHash(const QString& file)
{
QByteArray utf8File = file.toUtf8();
2013-10-03 20:52:02 +04:00
int64_t h;
if( file.isEmpty() ) {
2013-10-03 20:52:02 +04:00
return -1;
}
int len = utf8File.length();
h = c_jhash64((uint8_t *) utf8File.data(), len, 0);
2013-10-03 20:52:02 +04:00
return h;
}
bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
{
SyncJournalFileRecord record = _record;
QMutexLocker locker(&_mutex);
if (!_avoidReadFromDbOnNextSyncFilter.isEmpty()) {
// If we are a directory that should not be read from db next time, don't write the etag
QString prefix = record._path + "/";
foreach(const QString &it, _avoidReadFromDbOnNextSyncFilter) {
if (it.startsWith(prefix)) {
qDebug() << "Filtered writing the etag of" << prefix << "because it is a prefix of" << it;
record._etag = "_invalid_";
break;
}
}
}
qlonglong phash = getPHash(record._path);
if( checkConnect() ) {
QByteArray arr = record._path.toUtf8();
int plen = arr.length();
2013-11-13 14:16:00 +04:00
QString etag( record._etag );
if( etag.isEmpty() ) etag = "";
QString fileId( record._fileId);
if( fileId.isEmpty() ) fileId = "";
QString remotePerm (record._remotePerm);
if (remotePerm.isEmpty()) remotePerm = QString(); // have NULL in DB (vs empty)
_setFileRecordQuery->bindValue(1, QString::number(phash));
_setFileRecordQuery->bindValue(2, plen);
_setFileRecordQuery->bindValue(3, record._path );
_setFileRecordQuery->bindValue(4, record._inode );
_setFileRecordQuery->bindValue(5, 0 ); // uid Not used
_setFileRecordQuery->bindValue(6, 0 ); // gid Not used
_setFileRecordQuery->bindValue(7, record._mode );
_setFileRecordQuery->bindValue(8, QString::number(Utility::qDateTimeToTime_t(record._modtime)));
_setFileRecordQuery->bindValue(9, QString::number(record._type) );
_setFileRecordQuery->bindValue(10, etag );
_setFileRecordQuery->bindValue(11, fileId );
_setFileRecordQuery->bindValue(12, remotePerm );
2013-11-13 14:16:00 +04:00
if( !_setFileRecordQuery->exec() ) {
qWarning() << "Error SQL statement setFileRecord: " << _setFileRecordQuery->lastQuery() << " :"
<< _setFileRecordQuery->error();
return false;
}
2013-11-13 14:16:00 +04:00
qDebug() << _setFileRecordQuery->lastQuery() << phash << plen << record._path << record._inode
<< record._mode
<< QString::number(Utility::qDateTimeToTime_t(record._modtime)) << QString::number(record._type)
<< record._etag << record._fileId << record._remotePerm;
_setFileRecordQuery->reset();
return true;
} else {
qDebug() << "Failed to connect database.";
return false; // checkConnect failed.
}
}
bool SyncJournalDb::deleteFileRecord(const QString& filename, bool recursively)
{
QMutexLocker locker(&_mutex);
if( checkConnect() ) {
// if (!recursively) {
// always delete the actual file.
qlonglong phash = getPHash(filename);
_deleteFileRecordPhash->bindValue( 1, QString::number(phash) );
if( !_deleteFileRecordPhash->exec() ) {
qWarning() << "Exec error of SQL statement: "
<< _deleteFileRecordPhash->lastQuery()
<< " : " << _deleteFileRecordPhash->error();
return false;
}
qDebug() << _deleteFileRecordPhash->lastQuery() << phash << filename;
_deleteFileRecordPhash->reset();
if( recursively) {
_deleteFileRecordRecursively->bindValue(1, filename);
if( !_deleteFileRecordRecursively->exec() ) {
qWarning() << "Exec error of SQL statement: "
<< _deleteFileRecordRecursively->lastQuery()
<< " : " << _deleteFileRecordRecursively->error();
return false;
}
qDebug() << _deleteFileRecordRecursively->lastQuery() << filename;
_deleteFileRecordRecursively->reset();
}
return true;
} else {
qDebug() << "Failed to connect database.";
return false; // checkConnect failed.
}
}
SyncJournalFileRecord SyncJournalDb::getFileRecord( const QString& filename )
{
QMutexLocker locker(&_mutex);
2013-10-03 21:52:09 +04:00
qlonglong phash = getPHash( filename );
SyncJournalFileRecord rec;
if( checkConnect() ) {
_getFileRecordQuery->bindValue(1, QString::number(phash));
2013-11-13 14:16:00 +04:00
if (!_getFileRecordQuery->exec()) {
QString err = _getFileRecordQuery->error();
2013-11-13 14:16:00 +04:00
qDebug() << "Error creating prepared statement: " << _getFileRecordQuery->lastQuery() << ", Error:" << err;;
2013-10-03 21:52:09 +04:00
return rec;
}
2013-11-13 14:16:00 +04:00
if( _getFileRecordQuery->next() ) {
rec._path = _getFileRecordQuery->stringValue(0);
rec._inode = _getFileRecordQuery->intValue(1);
//rec._uid = _getFileRecordQuery->value(2).toInt(&ok); Not Used
//rec._gid = _getFileRecordQuery->value(3).toInt(&ok); Not Used
rec._mode = _getFileRecordQuery->intValue(4);
rec._modtime = Utility::qDateTimeFromTime_t(_getFileRecordQuery->int64Value(5));
rec._type = _getFileRecordQuery->intValue(6);
rec._etag = _getFileRecordQuery->baValue(7);
rec._fileId = _getFileRecordQuery->baValue(8);
rec._remotePerm = _getFileRecordQuery->baValue(9);
2013-11-13 14:16:00 +04:00
_getFileRecordQuery->reset();
2013-10-03 20:52:02 +04:00
} else {
QString err = _getFileRecordQuery->error();
qDebug() << "No journal entry found for " << filename;
}
}
return rec;
}
bool SyncJournalDb::postSyncCleanup(const QSet<QString> &items )
{
QMutexLocker locker(&_mutex);
2013-11-20 21:19:14 +04:00
if( !checkConnect() ) {
return false;
2013-11-20 21:19:14 +04:00
}
SqlQuery query(_db);
query.prepare("SELECT phash, path FROM metadata order by path");
if (!query.exec()) {
QString err = query.error();
qDebug() << "Error creating prepared statement: " << query.lastQuery() << ", Error:" << err;;
return false;
}
QStringList superfluousItems;
while(query.next()) {
const QString file = query.stringValue(1);
bool contained = items.contains(file);
if( !contained ) {
superfluousItems.append(query.stringValue(0));
}
}
if( superfluousItems.count() ) {
QString sql = "DELETE FROM metadata WHERE phash in ("+ superfluousItems.join(",")+")";
qDebug() << "Sync Journal cleanup: " << sql;
SqlQuery delQuery(_db);
delQuery.prepare(sql);
if( !delQuery.exec() ) {
QString err = delQuery.error();
qDebug() << "Error removing superfluous journal entries: " << delQuery.lastQuery() << ", Error:" << err;;
return false;
}
}
return true;
}
int SyncJournalDb::getFileRecordCount()
{
QMutexLocker locker(&_mutex);
2013-11-20 21:19:14 +04:00
if( !checkConnect() ) {
return -1;
2013-11-20 21:19:14 +04:00
}
SqlQuery query(_db);
query.prepare("SELECT COUNT(*) FROM metadata");
if (!query.exec()) {
QString err = query.error();
qDebug() << "Error creating prepared statement: " << query.lastQuery() << ", Error:" << err;;
return 0;
}
if (query.next()) {
int count = query.intValue(0);
return count;
}
return 0;
}
static void toDownloadInfo(SqlQuery &query, SyncJournalDb::DownloadInfo * res)
{
bool ok = true;
res->_tmpfile = query.stringValue(0);
res->_etag = query.baValue(1);
res->_errorCount = query.intValue(2);
res->_valid = ok;
}
static bool deleteBatch(SqlQuery & query, const QStringList & entries, const QString & name)
{
if (entries.isEmpty())
return true;
qDebug() << "Removing stale " << qPrintable(name) << " entries: " << entries.join(", ");
// FIXME: Was ported from execBatch, check if correct!
foreach( const QString& entry, entries ) {
query.bindValue(1, entry);
if (!query.exec()) {
QString err = query.error();
qDebug() << "Error removing stale " << qPrintable(name) << " entries: "
<< query.lastQuery() << ", Error:" << err;
return false;
}
}
query.reset();
return true;
}
SyncJournalDb::DownloadInfo SyncJournalDb::getDownloadInfo(const QString& file)
{
QMutexLocker locker(&_mutex);
DownloadInfo res;
if( checkConnect() ) {
_getDownloadInfoQuery->bindValue(1, file);
2013-11-13 14:16:00 +04:00
if (!_getDownloadInfoQuery->exec()) {
QString err = _getDownloadInfoQuery->error();
2013-11-13 14:16:00 +04:00
qDebug() << "Database error for file " << file << " : " << _getDownloadInfoQuery->lastQuery() << ", Error:" << err;;
return res;
}
2013-11-13 14:16:00 +04:00
if( _getDownloadInfoQuery->next() ) {
toDownloadInfo(*_getDownloadInfoQuery, &res);
} else {
res._valid = false;
}
_getDownloadInfoQuery->reset();
}
return res;
}
void SyncJournalDb::setDownloadInfo(const QString& file, const SyncJournalDb::DownloadInfo& i)
{
QMutexLocker locker(&_mutex);
2013-11-20 21:19:14 +04:00
if( !checkConnect() ) {
return;
2013-11-20 21:19:14 +04:00
}
if (i._valid) {
_setDownloadInfoQuery->bindValue(1, file);
_setDownloadInfoQuery->bindValue(2, i._tmpfile);
_setDownloadInfoQuery->bindValue(3, i._etag );
_setDownloadInfoQuery->bindValue(4, i._errorCount );
2013-11-13 14:16:00 +04:00
if( !_setDownloadInfoQuery->exec() ) {
qWarning() << "Exec error of SQL statement: " << _setDownloadInfoQuery->lastQuery() << " :" << _setDownloadInfoQuery->error();
return;
}
2013-11-13 14:16:00 +04:00
qDebug() << _setDownloadInfoQuery->lastQuery() << file << i._tmpfile << i._etag << i._errorCount;
_setDownloadInfoQuery->reset();
2013-11-13 14:16:00 +04:00
} else {
_deleteDownloadInfoQuery->bindValue( 1, file );
2013-11-13 14:16:00 +04:00
if( !_deleteDownloadInfoQuery->exec() ) {
qWarning() << "Exec error of SQL statement: " << _deleteDownloadInfoQuery->lastQuery() << " : " << _deleteDownloadInfoQuery->error();
return;
}
qDebug() << _deleteDownloadInfoQuery->lastQuery() << file;
_deleteDownloadInfoQuery->reset();
}
}
QVector<SyncJournalDb::DownloadInfo> SyncJournalDb::getAndDeleteStaleDownloadInfos(const QSet<QString>& keep)
{
2014-09-05 14:48:45 +04:00
QVector<SyncJournalDb::DownloadInfo> empty_result;
QMutexLocker locker(&_mutex);
if (!checkConnect()) {
2014-09-05 14:48:45 +04:00
return empty_result;
}
SqlQuery query(_db);
// The selected values *must* match the ones expected by toDownloadInfo().
query.prepare("SELECT tmpfile, etag, errorcount, path FROM downloadinfo");
if (!query.exec()) {
QString err = query.error();
qDebug() << "Error creating prepared statement: " << query.lastQuery() << ", Error:" << err;
2014-09-05 14:48:45 +04:00
return empty_result;
}
QStringList superfluousPaths;
QVector<SyncJournalDb::DownloadInfo> deleted_entries;
while (query.next()) {
const QString file = query.stringValue(3); // path
if (!keep.contains(file)) {
superfluousPaths.append(file);
DownloadInfo info;
toDownloadInfo(query, &info);
deleted_entries.append(info);
}
}
if (!deleteBatch(*_deleteDownloadInfoQuery, superfluousPaths, "downloadinfo"))
2014-09-05 14:48:45 +04:00
return empty_result;
return deleted_entries;
}
SyncJournalDb::UploadInfo SyncJournalDb::getUploadInfo(const QString& file)
{
QMutexLocker locker(&_mutex);
UploadInfo res;
if( checkConnect() ) {
_getUploadInfoQuery->bindValue(1, file);
2013-11-13 14:16:00 +04:00
if (!_getUploadInfoQuery->exec()) {
QString err = _getUploadInfoQuery->error();
2013-11-13 14:16:00 +04:00
qDebug() << "Database error for file " << file << " : " << _getUploadInfoQuery->lastQuery() << ", Error:" << err;
return res;
}
2013-11-13 14:16:00 +04:00
if( _getUploadInfoQuery->next() ) {
bool ok = true;
res._chunk = _getUploadInfoQuery->intValue(0);
res._transferid = _getUploadInfoQuery->intValue(1);
res._errorCount = _getUploadInfoQuery->intValue(2);
res._size = _getUploadInfoQuery->int64Value(3);
res._modtime = Utility::qDateTimeFromTime_t(_getUploadInfoQuery->int64Value(4));
res._valid = ok;
}
_getUploadInfoQuery->reset();
}
return res;
}
void SyncJournalDb::setUploadInfo(const QString& file, const SyncJournalDb::UploadInfo& i)
{
QMutexLocker locker(&_mutex);
2013-11-20 21:19:14 +04:00
if( !checkConnect() ) {
return;
2013-11-20 21:19:14 +04:00
}
if (i._valid) {
_setUploadInfoQuery->bindValue(1, file);
_setUploadInfoQuery->bindValue(2, i._chunk);
_setUploadInfoQuery->bindValue(3, i._transferid );
_setUploadInfoQuery->bindValue(4, i._errorCount );
_setUploadInfoQuery->bindValue(5, i._size );
_setUploadInfoQuery->bindValue(6, Utility::qDateTimeToTime_t(i._modtime) );
2013-11-13 14:16:00 +04:00
if( !_setUploadInfoQuery->exec() ) {
qWarning() << "Exec error of SQL statement: " << _setUploadInfoQuery->lastQuery() << " :" << _setUploadInfoQuery->error();
return;
}
2013-11-13 14:16:00 +04:00
qDebug() << _setUploadInfoQuery->lastQuery() << file << i._chunk << i._transferid << i._errorCount;
_setUploadInfoQuery->reset();
} else {
_deleteUploadInfoQuery->bindValue(1, file);
2013-11-13 14:16:00 +04:00
if( !_deleteUploadInfoQuery->exec() ) {
qWarning() << "Exec error of SQL statement: " << _deleteUploadInfoQuery->lastQuery() << " : " << _deleteUploadInfoQuery->error();
return;
}
qDebug() << _deleteUploadInfoQuery->lastQuery() << file;
_deleteUploadInfoQuery->reset();
}
}
bool SyncJournalDb::deleteStaleUploadInfos(const QSet<QString> &keep)
{
QMutexLocker locker(&_mutex);
if (!checkConnect()) {
return false;
}
SqlQuery query(_db);
query.prepare("SELECT path FROM uploadinfo");
if (!query.exec()) {
QString err = query.error();
qDebug() << "Error creating prepared statement: " << query.lastQuery() << ", Error:" << err;
return false;
}
QStringList superfluousPaths;
while (query.next()) {
const QString file = query.stringValue(0);
if (!keep.contains(file)) {
superfluousPaths.append(file);
}
}
return deleteBatch(*_deleteUploadInfoQuery, superfluousPaths, "uploadinfo");
}
SyncJournalBlacklistRecord SyncJournalDb::blacklistEntry( const QString& file )
{
QMutexLocker locker(&_mutex);
SyncJournalBlacklistRecord entry;
if( file.isEmpty() ) return entry;
// SELECT lastTryEtag, lastTryModtime, retrycount, errorstring
if( checkConnect() ) {
_blacklistQuery->bindValue( 1, file );
if( _blacklistQuery->exec() ){
if( _blacklistQuery->next() ) {
entry._lastTryEtag = _blacklistQuery->baValue(0);
entry._lastTryModtime = _blacklistQuery->int64Value(1);
entry._retryCount = _blacklistQuery->intValue(2);
entry._errorString = _blacklistQuery->stringValue(3);
entry._file = file;
}
} else {
qWarning() << "Exec error blacklist: " << _blacklistQuery->lastQuery() << " : "
<< _blacklistQuery->error();
}
_blacklistQuery->reset();
}
return entry;
}
bool SyncJournalDb::deleteStaleBlacklistEntries(const QSet<QString> &keep)
{
QMutexLocker locker(&_mutex);
if (!checkConnect()) {
return false;
}
SqlQuery query(_db);
query.prepare("SELECT path FROM blacklist");
if (!query.exec()) {
QString err = query.error();
qDebug() << "Error creating prepared statement: " << query.lastQuery() << ", Error:" << err;
return false;
}
QStringList superfluousPaths;
while (query.next()) {
const QString file = query.stringValue(0);
if (!keep.contains(file)) {
superfluousPaths.append(file);
}
}
SqlQuery delQuery(_db);
delQuery.prepare("DELETE FROM blacklist WHERE path = ?");
return deleteBatch(delQuery, superfluousPaths, "blacklist");
}
2013-12-03 17:47:32 +04:00
int SyncJournalDb::blackListEntryCount()
{
int re = 0;
QMutexLocker locker(&_mutex);
if( checkConnect() ) {
SqlQuery query("SELECT count(*) FROM blacklist", _db);
if( ! query.exec() ) {
2013-12-03 17:47:32 +04:00
sqlFail("Count number of blacklist entries failed", query);
}
if( query.next() ) {
re = query.intValue(0);
2013-12-03 17:47:32 +04:00
}
}
return re;
}
int SyncJournalDb::wipeBlacklist()
{
QMutexLocker locker(&_mutex);
if( checkConnect() ) {
SqlQuery query(_db);
query.prepare("DELETE FROM blacklist");
if( ! query.exec() ) {
sqlFail("Deletion of whole blacklist failed", query);
2013-12-03 17:47:32 +04:00
return -1;
}
return query.numRowsAffected();
}
return -1;
}
void SyncJournalDb::wipeBlacklistEntry( const QString& file )
{
if( file.isEmpty() ) {
return;
}
QMutexLocker locker(&_mutex);
2013-11-20 21:19:14 +04:00
if( checkConnect() ) {
SqlQuery query(_db);
query.prepare("DELETE FROM blacklist WHERE path=?1");
query.bindValue(1, file);
2013-11-20 21:19:14 +04:00
if( ! query.exec() ) {
sqlFail("Deletion of blacklist item failed.", query);
2013-11-20 21:19:14 +04:00
}
}
}
void SyncJournalDb::updateBlacklistEntry( const SyncJournalBlacklistRecord& item )
{
2013-11-20 21:19:14 +04:00
if( !checkConnect() ) {
return;
}
int retries = 0;
SyncJournalBlacklistRecord rec = blacklistEntry( item._file );
QMutexLocker locker(&_mutex);
bool haveRecord = false;
if( rec.isValid() ) {
haveRecord = true;
retries = rec._retryCount;
}
SqlQuery iQuery(_db);
if( haveRecord ) {
retries--;
if( retries < 0 ) retries = 0;
iQuery.prepare( "UPDATE blacklist SET lastTryEtag = ?1, lastTryModtime = ?2, "
"retrycount = ?3, errorstring = ?4 WHERE path=?5;");
iQuery.bindValue(1, item._lastTryEtag);
iQuery.bindValue(2, QString::number(item._lastTryModtime));
iQuery.bindValue(3, retries);
iQuery.bindValue(4, item._errorString);
iQuery.bindValue(5, item._file);
} else {
// there is no entry yet.
iQuery.prepare("INSERT INTO blacklist (path, lastTryEtag, lastTryModtime, retrycount, errorstring) "
"VALUES (?1, ?2, ?3, ?4, ?5);");
iQuery.bindValue(1, item._file );
iQuery.bindValue(2, item._lastTryEtag);
iQuery.bindValue(3, QString::number(item._lastTryModtime));
iQuery.bindValue(4, item._retryCount);
iQuery.bindValue(5, item._errorString);
}
if( !iQuery.exec() ) {
QString bug = iQuery.error();
qDebug() << "SQL exec blacklistitem insert/update failed: "<< bug;
}
}
void SyncJournalDb::avoidRenamesOnNextSync(const QString& path)
{
QMutexLocker locker(&_mutex);
if( !checkConnect() ) {
return;
}
SqlQuery query(_db);
query.prepare("UPDATE metadata SET fileid = '', inode = '0' WHERE path == ?1 OR path LIKE(?2||'/%')");
query.bindValue(1, path);
query.bindValue(2, path);
if( !query.exec() ) {
qDebug() << Q_FUNC_INFO << "SQL error in avoidRenamesOnNextSync: "<< query.error();
} else {
qDebug() << Q_FUNC_INFO << query.lastQuery() << path << "(" << query.numRowsAffected() << " rows)";
}
// We also need to remove the ETags so the update phase refreshes the directory paths
// on the next sync
locker.unlock();
avoidReadFromDbOnNextSync(path);
}
void SyncJournalDb::avoidReadFromDbOnNextSync(const QString& fileName)
{
//Make sure that on the next sync, filName is not read from the DB but use the PROPFIND to
//get the info from the server
// We achieve that by clearing the etag of the parents directory recursively
QMutexLocker locker(&_mutex);
if( !checkConnect() ) {
return;
}
SqlQuery query(_db);
// This query will match entries for whitch the path is a prefix of fileName
query.prepare("UPDATE metadata SET md5='_invalid_' WHERE ?1 LIKE(path||'/%') AND type == 2;"); // CSYNC_FTW_TYPE_DIR == 2
query.bindValue(1, fileName);
if( !query.exec() ) {
qDebug() << Q_FUNC_INFO << "SQL error in avoidRenamesOnNextSync: "<< query.error();
} else {
qDebug() << Q_FUNC_INFO << query.lastQuery() << fileName << "(" << query.numRowsAffected() << " rows)";
}
// Prevent future overwrite of the etag for this sync
_avoidReadFromDbOnNextSyncFilter.append(fileName);
}
void SyncJournalDb::commit(const QString& context, bool startTrans)
{
QMutexLocker lock(&_mutex);
commitInternal(context, startTrans);
}
void SyncJournalDb::commitInternal(const QString& context, bool startTrans )
{
2014-10-13 19:28:47 +04:00
qDebug() << Q_FUNC_INFO << "Transaction commit " << context << (startTrans ? "and starting new transaction" : "");
commitTransaction();
if( startTrans ) {
startTransaction();
}
}
SyncJournalDb::~SyncJournalDb()
{
close();
}
bool SyncJournalDb::isConnected()
{
QMutexLocker lock(&_mutex);
return checkConnect();
}
bool SyncJournalDb::isUpdateFrom_1_5()
{
QMutexLocker lock(&_mutex);
checkConnect();
return _possibleUpgradeFromMirall_1_5;
}
} // namespace Mirall