Merge pull request #3951 from ckamm/checksum

Checksums stored in database #3735
This commit is contained in:
ckamm 2015-10-29 10:40:24 +01:00
Родитель 64756c5dce 496b1e907d
Коммит 251679253a
20 изменённых файлов: 550 добавлений и 210 удалений

Просмотреть файл

@ -13,6 +13,8 @@
#include "capabilities.h"
#include "configfile.h"
#include <QVariantMap>
namespace OCC {
@ -63,9 +65,28 @@ bool Capabilities::shareResharing() const
return _capabilities["files_sharing"].toMap()["resharing"].toBool();
}
QStringList Capabilities::supportedChecksumTypes() const
QList<QByteArray> Capabilities::supportedChecksumTypesAdvertised() const
{
return QStringList();
return QList<QByteArray>();
}
QList<QByteArray> Capabilities::supportedChecksumTypes() const
{
auto list = supportedChecksumTypesAdvertised();
QByteArray cfgType = ConfigFile().transmissionChecksum().toLatin1();
if (!cfgType.isEmpty()) {
list.prepend(cfgType);
}
return list;
}
QByteArray Capabilities::preferredChecksumType() const
{
auto list = supportedChecksumTypes();
if (list.isEmpty()) {
return QByteArray();
}
return list.first();
}
}

Просмотреть файл

@ -39,7 +39,15 @@ public:
bool sharePublicLinkEnforceExpireDate() const;
int sharePublicLinkExpireDateDays() const;
bool shareResharing() const;
QStringList supportedChecksumTypes() const;
/// Returns the checksum types the server explicitly advertises
QList<QByteArray> supportedChecksumTypesAdvertised() const;
/// Like supportedChecksumTypesRaw(), but includes the type from the config
QList<QByteArray> supportedChecksumTypes() const;
/// Returns the checksum type that should be used for new uploads.
QByteArray preferredChecksumType() const;
private:
QVariantMap _capabilities;

Просмотреть файл

@ -277,14 +277,14 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
/* Check and log the transmission checksum type */
ConfigFile cfg;
const QString checksumType = cfg.transmissionChecksum().toUpper();
const QString checksumType = cfg.transmissionChecksum();
/* if the checksum type is empty, it is not sent. No error */
if( !checksumType.isEmpty() ) {
if( checksumType == checkSumAdlerUpperC ||
if( checksumType == checkSumAdlerC ||
checksumType == checkSumMD5C ||
checksumType == checkSumSHA1C ) {
qDebug() << "Client sends and expects transmission checksum type" << checksumType;
qDebug() << "Client sends transmission checksum type" << checksumType;
} else {
qWarning() << "Unknown transmission checksum type from config" << checksumType;
}

Просмотреть файл

@ -316,6 +316,11 @@ void SqlQuery::bindValue(int pos, const QVariant& value)
Q_ASSERT( res == SQLITE_OK );
}
bool SqlQuery::nullValue(int index)
{
return sqlite3_column_type(_stmt, index) == SQLITE_NULL;
}
QString SqlQuery::stringValue(int index)
{
return QString::fromUtf16(static_cast<const ushort*>(sqlite3_column_text16(_stmt, index)));

Просмотреть файл

@ -66,6 +66,9 @@ public:
~SqlQuery();
QString error() const;
/// Checks whether the value at the given column index is NULL
bool nullValue(int index);
QString stringValue(int index);
int intValue(int index);
quint64 int64Value(int index);

Просмотреть файл

@ -351,7 +351,10 @@ void PropagateDownloadFileQNAM::start()
if (_resumeStart == _item->_size) {
qDebug() << "File is already complete, no need to download";
_tmpFile.close();
downloadFinished();
// Unfortunately we lost the checksum header, if any...
QByteArray noChecksumData;
downloadFinished(noChecksumData, noChecksumData);
return;
}
}
@ -532,11 +535,16 @@ void PropagateDownloadFileQNAM::slotGetFinished()
// Do checksum validation for the download. If there is no checksum header, the validator
// will also emit the validated() signal to continue the flow in slot downloadFinished()
// as this is (still) also correct.
TransmissionChecksumValidator *validator = new TransmissionChecksumValidator(_tmpFile.fileName(), this);
connect(validator, SIGNAL(validated(QByteArray)), this, SLOT(downloadFinished()));
connect(validator, SIGNAL(validationFailed(QString)), this, SLOT(slotChecksumFail(QString)));
validator->downloadValidation(job->reply()->rawHeader(checkSumHeaderC));
ValidateChecksumHeader *validator = new ValidateChecksumHeader(this);
connect(validator, SIGNAL(validated(QByteArray,QByteArray)),
SLOT(downloadFinished(QByteArray,QByteArray)));
connect(validator, SIGNAL(validationFailed(QString)),
SLOT(slotChecksumFail(QString)));
auto checksumHeader = job->reply()->rawHeader(checkSumHeaderC);
if (!downloadChecksumEnabled()) {
checksumHeader.clear();
}
validator->start(_tmpFile.fileName(), checksumHeader);
}
void PropagateDownloadFileQNAM::slotChecksumFail( const QString& errMsg )
@ -613,8 +621,13 @@ static void handleRecallFile(const QString &fn)
}
} // end namespace
void PropagateDownloadFileQNAM::downloadFinished()
void PropagateDownloadFileQNAM::downloadFinished(const QByteArray& checksumType, const QByteArray& checksum)
{
if (!checksumType.isEmpty()) {
_item->_transmissionChecksum = checksum;
_item->_transmissionChecksumType = checksumType;
}
QString fn = _propagator->getFilePath(_item->_file);
// In case of file name clash, report an error

Просмотреть файл

@ -117,7 +117,7 @@ public:
private slots:
void slotGetFinished();
void abort() Q_DECL_OVERRIDE;
void downloadFinished();
void downloadFinished(const QByteArray& checksumType, const QByteArray& checksum);
void slotDownloadProgress(qint64,qint64);
void slotChecksumFail( const QString& errMsg );

Просмотреть файл

@ -205,30 +205,48 @@ void PropagateUploadFileQNAM::start()
_stopWatch.start();
// do whatever is needed to add a checksum to the http upload request.
// in any case, the validator will emit signal startUpload to let the flow
// continue in slotStartUpload here.
TransmissionChecksumValidator *validator = new TransmissionChecksumValidator(filePath, this);
auto supportedChecksumTypes = _propagator->account()->capabilities().supportedChecksumTypes();
// If the config file does not specify a checksum type but the
// server supports it choose a type based on that.
if (validator->checksumType().isEmpty()) {
QStringList checksumTypes = _propagator->account()->capabilities().supportedChecksumTypes();
if (!checksumTypes.isEmpty()) {
// TODO: We might want to prefer some types over others instead
// of choosing the first.
validator->setChecksumType(checksumTypes.first());
// If we already have a checksum header and the checksum type is supported
// by the server, we keep that - otherwise recompute.
//
// Note: Currently we *always* recompute because we usually only upload
// files that have changed and thus have a new checksum. But if an earlier
// phase computed a checksum, this is where we would make use of it.
if (!_item->_transmissionChecksumType.isEmpty()) {
if (supportedChecksumTypes.contains(_item->_transmissionChecksumType)) {
// TODO: We could validate the old checksum and thereby determine whether
// an upload is necessary or not.
slotStartUpload(_item->_transmissionChecksumType, _item->_transmissionChecksum);
return;
}
}
connect(validator, SIGNAL(validated(QByteArray)), this, SLOT(slotStartUpload(QByteArray)));
validator->uploadValidation();
// Compute a new checksum.
auto computeChecksum = new ComputeChecksum(this);
if (uploadChecksumEnabled()) {
computeChecksum->setChecksumType(_propagator->account()->capabilities().preferredChecksumType());
} else {
computeChecksum->setChecksumType(QByteArray());
}
connect(computeChecksum, SIGNAL(done(QByteArray,QByteArray)),
SLOT(slotStartUpload(QByteArray,QByteArray)));
computeChecksum->start(filePath);
}
void PropagateUploadFileQNAM::slotStartUpload(const QByteArray& checksum)
void PropagateUploadFileQNAM::slotStartUpload(const QByteArray& checksumType, const QByteArray& checksum)
{
// Store the computed checksum in the database, if different
if (checksumType != _item->_transmissionChecksumType
|| checksum != _item->_transmissionChecksum) {
_item->_transmissionChecksum = checksum;
_item->_transmissionChecksumType = checksumType;
_propagator->_journal->updateFileRecordChecksum(
_item->_file, checksum, checksumType);
}
const QString fullFilePath = _propagator->getFilePath(_item->_file);
_item->_checksum = checksum;
if (!FileSystem::fileExists(fullFilePath)) {
done(SyncFileItem::SoftError, tr("File Removed"));
@ -458,6 +476,7 @@ void PropagateUploadFileQNAM::startNextChunk()
UploadDevice *device = new UploadDevice(&_propagator->_bandwidthManager);
qint64 chunkStart = 0;
qint64 currentChunkSize = fileSize;
bool isFinalChunk = false;
if (_chunkCount > 1) {
int sendingChunk = (_currentChunk + _startChunk) % _chunkCount;
// XOR with chunk size to make sure everything goes well if chunk size changes between runs
@ -474,15 +493,16 @@ void PropagateUploadFileQNAM::startNextChunk()
if( currentChunkSize == 0 ) { // if the last chunk pretends to be 0, its actually the full chunk size.
currentChunkSize = chunkSize();
}
if( !_item->_checksum.isEmpty() ) {
headers[checkSumHeaderC] = _item->_checksum;
}
isFinalChunk = true;
}
} else {
// checksum if its only one chunk
if( !_item->_checksum.isEmpty() ) {
headers[checkSumHeaderC] = _item->_checksum;
}
// if there's only one chunk, it's the final one
isFinalChunk = true;
}
if (isFinalChunk && !_item->_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_item->_transmissionChecksumType, _item->_transmissionChecksum);
}
if (! device->prepareAndOpen(_propagator->getFilePath(_item->_file), chunkStart, currentChunkSize)) {

Просмотреть файл

@ -195,7 +195,7 @@ private slots:
void startNextChunk();
void finalize(const SyncFileItem&);
void slotJobDestroyed(QObject *job);
void slotStartUpload(const QByteArray &checksum);
void slotStartUpload(const QByteArray& checksumType, const QByteArray& checksum);
private:
void startPollJob(const QString& path);

Просмотреть файл

@ -32,7 +32,6 @@ static const char checkSumHeaderC[] = "OC-Checksum";
static const char checkSumMD5C[] = "MD5";
static const char checkSumSHA1C[] = "SHA1";
static const char checkSumAdlerC[] = "Adler32";
static const char checkSumAdlerUpperC[] = "ADLER32";
/**
* @brief Declaration of the other propagation jobs

Просмотреть файл

@ -468,7 +468,7 @@ int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
// the file system in the DB, this is to avoid spurious upload on the next sync
item->_modtime = file->other.modtime;
_journal->setFileRecord(SyncJournalFileRecord(*item, _localPath + item->_file));
_journal->updateFileRecordMetadata(SyncJournalFileRecord(*item, _localPath + item->_file));
item->_should_update_metadata = false;
}
if (item->_isDirectory && file->should_update_metadata) {

Просмотреть файл

@ -165,7 +165,8 @@ public:
quint64 _inode;
QByteArray _fileId;
QByteArray _remotePerm;
QByteArray _checksum;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
QString _directDownloadUrl;
QString _directDownloadCookies;

Просмотреть файл

@ -206,6 +206,8 @@ bool SyncJournalDb::checkConnect()
"md5 VARCHAR(32)," /* This is the etag. Called md5 for compatibility */
// updateDatabaseStructure() will add a fileid column
// updateDatabaseStructure() will add a remotePerm column
// updateDatabaseStructure() will add a transmissionChecksum column
// updateDatabaseStructure() will add a transmissionChecksumTypeId column
"PRIMARY KEY(phash)"
");");
@ -271,6 +273,14 @@ bool SyncJournalDb::checkConnect()
return sqlFail("Create table selectivesync", createQuery);
}
// create the checksumtype table.
createQuery.prepare("CREATE TABLE IF NOT EXISTS checksumtype("
"id INTEGER PRIMARY KEY,"
"name TEXT UNIQUE"
");");
if (!createQuery.exec()) {
return sqlFail("Create table version", createQuery);
}
createQuery.prepare("CREATE TABLE IF NOT EXISTS version("
@ -346,13 +356,30 @@ bool SyncJournalDb::checkConnect()
}
_getFileRecordQuery.reset(new SqlQuery(_db));
_getFileRecordQuery->prepare("SELECT path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote FROM "
"metadata WHERE phash=?1" );
_getFileRecordQuery->prepare(
"SELECT path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize,"
" ignoredChildrenRemote, transmissionChecksum, checksumtype.name"
" FROM metadata"
" LEFT JOIN checksumtype ON metadata.transmissionChecksumTypeId == checksumtype.id"
" 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, filesize, ignoredChildrenRemote) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14);" );
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, transmissionChecksum, transmissionChecksumTypeId) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16);" );
_setFileRecordChecksumQuery.reset(new SqlQuery(_db) );
_setFileRecordChecksumQuery->prepare(
"UPDATE metadata"
" SET transmissionChecksum = ?2, transmissionChecksumTypeId = ?3"
" WHERE phash == ?1;");
_setFileRecordMetadataQuery.reset(new SqlQuery(_db) );
_setFileRecordMetadataQuery->prepare(
"UPDATE metadata"
" SET inode=?2, mode=?3, modtime=?4, type=?5, md5=?6, fileid=?7,"
" remotePerm=?8, filesize=?9, ignoredChildrenRemote=?10"
" WHERE phash == ?1;");
_getDownloadInfoQuery.reset(new SqlQuery(_db) );
_getDownloadInfoQuery->prepare( "SELECT tmpfile, etag, errorcount FROM "
@ -403,6 +430,12 @@ bool SyncJournalDb::checkConnect()
_getSelectiveSyncListQuery.reset(new SqlQuery(_db));
_getSelectiveSyncListQuery->prepare("SELECT path FROM selectivesync WHERE type=?1");
_getChecksumTypeIdQuery.reset(new SqlQuery(_db));
_getChecksumTypeIdQuery->prepare("SELECT id FROM checksumtype WHERE name=?1");
_insertChecksumTypeQuery.reset(new SqlQuery(_db));
_insertChecksumTypeQuery->prepare("INSERT OR IGNORE INTO checksumtype (name) VALUES (?1)");
// don't start a new transaction now
commitInternal(QString("checkConnect End"), false);
@ -527,6 +560,27 @@ bool SyncJournalDb::updateMetadataTableStructure()
}
commitInternal("update database structure: add ignoredChildrenRemote col");
}
if( columns.indexOf(QLatin1String("transmissionChecksum")) == -1 ) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN transmissionChecksum TEXT;");
if( !query.exec()) {
sqlFail("updateMetadataTableStructure: add transmissionChecksum column", query);
re = false;
}
commitInternal("update database structure: add transmissionChecksum col");
}
if( columns.indexOf(QLatin1String("transmissionChecksumTypeId")) == -1 ) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN transmissionChecksumTypeId INTEGER;");
if( !query.exec()) {
sqlFail("updateMetadataTableStructure: add transmissionChecksumTypeId column", query);
re = false;
}
commitInternal("update database structure: add transmissionChecksumTypeId col");
}
return re;
}
@ -627,6 +681,7 @@ bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
if( fileId.isEmpty() ) fileId = "";
QString remotePerm (record._remotePerm);
if (remotePerm.isEmpty()) remotePerm = QString(); // have NULL in DB (vs empty)
int checksumTypeId = mapChecksumType(record._transmissionChecksumType);
_setFileRecordQuery->reset();
_setFileRecordQuery->bindValue(1, QString::number(phash));
_setFileRecordQuery->bindValue(2, plen);
@ -642,6 +697,8 @@ bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
_setFileRecordQuery->bindValue(12, remotePerm );
_setFileRecordQuery->bindValue(13, record._fileSize );
_setFileRecordQuery->bindValue(14, record._serverHasIgnoredFiles ? 1:0);
_setFileRecordQuery->bindValue(15, record._transmissionChecksum );
_setFileRecordQuery->bindValue(16, checksumTypeId );
if( !_setFileRecordQuery->exec() ) {
qWarning() << "Error SQL statement setFileRecord: " << _setFileRecordQuery->lastQuery() << " :"
@ -652,7 +709,8 @@ bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
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 << record._fileSize << (record._serverHasIgnoredFiles ? 1:0);
<< record._etag << record._fileId << record._remotePerm << record._fileSize << (record._serverHasIgnoredFiles ? 1:0)
<< record._transmissionChecksum << record._transmissionChecksumType << checksumTypeId;
_setFileRecordQuery->reset();
return true;
@ -732,6 +790,10 @@ SyncJournalFileRecord SyncJournalDb::getFileRecord( const QString& filename )
rec._remotePerm = _getFileRecordQuery->baValue(9);
rec._fileSize = _getFileRecordQuery->int64Value(10);
rec._serverHasIgnoredFiles = (_getFileRecordQuery->intValue(11) > 0);
rec._transmissionChecksum = _getFileRecordQuery->baValue(12);
if( !_getFileRecordQuery->nullValue(13) ) {
rec._transmissionChecksumType = _getFileRecordQuery->baValue(13);
}
} else {
QString err = _getFileRecordQuery->error();
qDebug() << "No journal entry found for " << filename;
@ -820,6 +882,86 @@ int SyncJournalDb::getFileRecordCount()
return 0;
}
bool SyncJournalDb::updateFileRecordChecksum(const QString& filename,
const QByteArray& transmisisonChecksum,
const QByteArray& transmissionChecksumType)
{
QMutexLocker locker(&_mutex);
qlonglong phash = getPHash(filename);
if( !checkConnect() ) {
qDebug() << "Failed to connect database.";
return false;
}
int checksumTypeId = mapChecksumType(transmissionChecksumType);
auto & query = _setFileRecordChecksumQuery;
query->reset();
query->bindValue(1, QString::number(phash));
query->bindValue(2, transmisisonChecksum);
query->bindValue(3, checksumTypeId);
if( !query->exec() ) {
qWarning() << "Error SQL statement setFileRecordChecksumQuery: "
<< query->lastQuery() << " :"
<< query->error();
return false;
}
qDebug() << query->lastQuery() << phash << transmisisonChecksum
<< transmissionChecksumType << checksumTypeId;
query->reset();
return true;
}
bool SyncJournalDb::updateFileRecordMetadata(const SyncJournalFileRecord& record)
{
QMutexLocker locker(&_mutex);
qlonglong phash = getPHash(record._path);
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)
if( !checkConnect() ) {
qDebug() << "Failed to connect database.";
return false;
}
auto & query = _setFileRecordMetadataQuery;
query->reset();
query->bindValue(1, QString::number(phash));
query->bindValue(2, record._inode);
query->bindValue(3, record._mode);
query->bindValue(4, QString::number(Utility::qDateTimeToTime_t(record._modtime)));
query->bindValue(5, QString::number(record._type));
query->bindValue(6, etag);
query->bindValue(7, fileId);
query->bindValue(8, remotePerm);
query->bindValue(9, record._fileSize);
query->bindValue(10, record._serverHasIgnoredFiles ? 1 : 0);
if( !query->exec() ) {
qWarning() << "Error SQL statement setFileRecordMetadataQuery: "
<< query->lastQuery() << " :"
<< query->error();
return false;
}
qDebug() << query->lastQuery() << record._path << record._inode << record._mode << record._modtime
<< record._type << etag << fileId << remotePerm << record._fileSize
<< record._serverHasIgnoredFiles;
query->reset();
return true;
}
static void toDownloadInfo(SqlQuery &query, SyncJournalDb::DownloadInfo * res)
{
bool ok = true;
@ -1389,6 +1531,39 @@ void SyncJournalDb::forceRemoteDiscoveryNextSyncLocked()
}
}
int SyncJournalDb::mapChecksumType(const QByteArray& checksumType)
{
if (checksumType.isEmpty()) {
return 0;
}
// Ensure the checksum type is in the db
_insertChecksumTypeQuery->reset();
_insertChecksumTypeQuery->bindValue(1, checksumType);
if( !_insertChecksumTypeQuery->exec() ) {
qWarning() << "Error SQL statement insertChecksumType: "
<< _insertChecksumTypeQuery->lastQuery() << " :"
<< _insertChecksumTypeQuery->error();
return 0;
}
// Retrieve the id
_getChecksumTypeIdQuery->reset();
_getChecksumTypeIdQuery->bindValue(1, checksumType);
if( !_getChecksumTypeIdQuery->exec() ) {
qWarning() << "Error SQL statement getChecksumTypeId: "
<< _getChecksumTypeIdQuery->lastQuery() << " :"
<< _getChecksumTypeIdQuery->error();
return 0;
}
if( !_getChecksumTypeIdQuery->next() ) {
qDebug() << "No checksum type mapping found for" << checksumType;
return 0;
}
return _getChecksumTypeIdQuery->intValue(0);
}
void SyncJournalDb::commit(const QString& context, bool startTrans)
{

Просмотреть файл

@ -42,6 +42,10 @@ public:
bool setFileRecord( const SyncJournalFileRecord& record );
bool deleteFileRecord( const QString& filename, bool recursively = false );
int getFileRecordCount();
bool updateFileRecordChecksum(const QString& filename,
const QByteArray& transmisisonChecksum,
const QByteArray& transmissionChecksumType);
bool updateFileRecordMetadata(const SyncJournalFileRecord& record);
bool exists();
void walCheckpoint();
@ -153,12 +157,19 @@ private:
// Same as forceRemoteDiscoveryNextSync but without acquiring the lock
void forceRemoteDiscoveryNextSyncLocked();
// Returns the integer id of the checksum type
//
// Returns 0 on failure and for empty checksum types.
int mapChecksumType(const QByteArray& checksumType);
SqlDatabase _db;
QString _dbFile;
QMutex _mutex; // Public functions are protected with the mutex.
int _transaction;
QScopedPointer<SqlQuery> _getFileRecordQuery;
QScopedPointer<SqlQuery> _setFileRecordQuery;
QScopedPointer<SqlQuery> _setFileRecordChecksumQuery;
QScopedPointer<SqlQuery> _setFileRecordMetadataQuery;
QScopedPointer<SqlQuery> _getDownloadInfoQuery;
QScopedPointer<SqlQuery> _setDownloadInfoQuery;
QScopedPointer<SqlQuery> _deleteDownloadInfoQuery;
@ -170,6 +181,8 @@ private:
QScopedPointer<SqlQuery> _getErrorBlacklistQuery;
QScopedPointer<SqlQuery> _setErrorBlacklistQuery;
QScopedPointer<SqlQuery> _getSelectiveSyncListQuery;
QScopedPointer<SqlQuery> _getChecksumTypeIdQuery;
QScopedPointer<SqlQuery> _insertChecksumTypeQuery;
/* This is the list of paths we called avoidReadFromDbOnNextSync on.
* It means that they should not be written to the DB in any case since doing

Просмотреть файл

@ -35,7 +35,9 @@ SyncJournalFileRecord::SyncJournalFileRecord()
SyncJournalFileRecord::SyncJournalFileRecord(const SyncFileItem &item, const QString &localFileName)
: _path(item._file), _modtime(Utility::qDateTimeFromTime_t(item._modtime)),
_type(item._type), _etag(item._etag), _fileId(item._fileId), _fileSize(item._size),
_remotePerm(item._remotePerm), _mode(0), _serverHasIgnoredFiles(item._serverHasIgnoredFiles)
_remotePerm(item._remotePerm), _mode(0), _serverHasIgnoredFiles(item._serverHasIgnoredFiles),
_transmissionChecksum(item._transmissionChecksum),
_transmissionChecksumType(item._transmissionChecksumType)
{
// use the "old" inode coming with the item for the case where the
// filesystem stat fails. That can happen if the the file was removed
@ -154,9 +156,12 @@ bool operator==(const SyncJournalFileRecord & lhs,
&& lhs._type == rhs._type
&& lhs._etag == rhs._etag
&& lhs._fileId == rhs._fileId
&& lhs._fileSize == rhs._fileSize
&& lhs._remotePerm == rhs._remotePerm
&& lhs._mode == rhs._mode
&& lhs._fileSize == rhs._fileSize;
&& lhs._serverHasIgnoredFiles == rhs._serverHasIgnoredFiles
&& lhs._transmissionChecksum == rhs._transmissionChecksum
&& lhs._transmissionChecksumType == rhs._transmissionChecksumType;
}
}

Просмотреть файл

@ -47,6 +47,8 @@ public:
QByteArray _remotePerm;
int _mode;
bool _serverHasIgnoredFiles;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
};
bool OWNCLOUDSYNC_EXPORT

Просмотреть файл

@ -16,133 +16,139 @@
#include "transmissionchecksumvalidator.h"
#include "syncfileitem.h"
#include "propagatorjobs.h"
#include "configfile.h"
#include "account.h"
#include <qtconcurrentrun.h>
namespace OCC {
TransmissionChecksumValidator::TransmissionChecksumValidator(const QString& filePath, QObject *parent)
: QObject(parent),
_filePath(filePath)
QByteArray makeChecksumHeader(const QByteArray& checksumType, const QByteArray& checksum)
{
// If the config file specifies a checksum type, use that.
ConfigFile cfg;
_checksumType = cfg.transmissionChecksum();
QByteArray header = checksumType;
header.append(':');
header.append(checksum);
return header;
}
void TransmissionChecksumValidator::setChecksumType(const QString& type)
bool parseChecksumHeader(const QByteArray& header, QByteArray* type, QByteArray* checksum)
{
if (header.isEmpty()) {
type->clear();
checksum->clear();
return true;
}
const auto idx = header.indexOf(':');
if (idx < 0) {
return false;
}
*type = header.left(idx);
*checksum = header.mid(idx + 1);
return true;
}
bool uploadChecksumEnabled()
{
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD").isEmpty();
return enabled;
}
bool downloadChecksumEnabled()
{
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_DOWNLOAD").isEmpty();
return enabled;
}
ComputeChecksum::ComputeChecksum(QObject* parent)
: QObject(parent)
{
}
void ComputeChecksum::setChecksumType(const QByteArray& type)
{
_checksumType = type;
}
QString TransmissionChecksumValidator::checksumType() const
QByteArray ComputeChecksum::checksumType() const
{
return _checksumType;
}
void TransmissionChecksumValidator::uploadValidation()
void ComputeChecksum::start(const QString& filePath)
{
const QString csType = checksumType();
if( csType.isEmpty() ) {
// if there is no checksum defined, continue to upload
emit validated(QByteArray());
} else {
// Calculate the checksum in a different thread first.
// Calculate the checksum in a different thread first.
connect( &_watcher, SIGNAL(finished()),
this, SLOT(slotCalculationDone()),
Qt::UniqueConnection );
if( csType == checkSumMD5C ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, filePath));
connect( &_watcher, SIGNAL(finished()),
this, SLOT(slotUploadChecksumCalculated()));
if( csType == checkSumMD5C ) {
_checksumHeader = checkSumMD5C;
_checksumHeader += ":";
_watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, _filePath));
} else if( csType == checkSumSHA1C ) {
_checksumHeader = checkSumSHA1C;
_checksumHeader += ":";
_watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1, _filePath));
}
#ifdef ZLIB_FOUND
else if( csType == checkSumAdlerC) {
_checksumHeader = checkSumAdlerC;
_checksumHeader += ":";
_watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, _filePath));
}
#endif
else {
// for an unknown checksum, continue to upload
emit validated(QByteArray());
}
}
}
void TransmissionChecksumValidator::slotUploadChecksumCalculated( )
{
QByteArray checksum = _watcher.future().result();
if( !checksum.isEmpty() ) {
checksum.prepend( _checksumHeader );
}
emit validated(checksum);
}
void TransmissionChecksumValidator::downloadValidation( const QByteArray& checksumHeader )
{
// if the incoming header is empty, there was no checksum header, and
// no validation can happen. Just continue.
const QString csType = checksumType();
// for empty checksum type, everything is valid.
if( csType.isEmpty() ) {
emit validated(QByteArray());
return;
}
int indx = checksumHeader.indexOf(':');
if( indx < 0 ) {
qDebug() << "Checksum header malformed:" << checksumHeader;
emit validationFailed(tr("The checksum header is malformed.")); // show must go on - even not validated.
return;
}
const QByteArray type = checksumHeader.left(indx).toUpper();
_expectedHash = checksumHeader.mid(indx+1);
connect( &_watcher, SIGNAL(finished()), this, SLOT(slotDownloadChecksumCalculated()) );
// start the calculation in different thread
if( type == checkSumMD5C ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, _filePath));
} else if( type == checkSumSHA1C ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcSha1, _filePath));
} else if( csType == checkSumSHA1C ) {
_watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1, filePath));
}
#ifdef ZLIB_FOUND
else if( type == checkSumAdlerUpperC ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, _filePath));
else if( csType == checkSumAdlerC) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, filePath));
}
#endif
else {
qDebug() << "Unknown checksum type" << type;
// for an unknown checksum or no checksum, we're done right now
if( !csType.isEmpty() ) {
qDebug() << "Unknown checksum type:" << csType;
}
emit done(QByteArray(), QByteArray());
}
}
void ComputeChecksum::slotCalculationDone()
{
QByteArray checksum = _watcher.future().result();
emit done(_checksumType, checksum);
}
ValidateChecksumHeader::ValidateChecksumHeader(QObject *parent)
: QObject(parent)
{
}
void ValidateChecksumHeader::start(const QString& filePath, const QByteArray& checksumHeader)
{
// If the incoming header is empty no validation can happen. Just continue.
if( checksumHeader.isEmpty() ) {
emit validated(QByteArray(), QByteArray());
return;
}
if( !parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum) ) {
qDebug() << "Checksum header malformed:" << checksumHeader;
emit validationFailed(tr("The checksum header is malformed."));
return;
}
auto calculator = new ComputeChecksum(this);
calculator->setChecksumType(_expectedChecksumType);
connect(calculator, SIGNAL(done(QByteArray,QByteArray)),
SLOT(slotChecksumCalculated(QByteArray,QByteArray)));
calculator->start(filePath);
}
void TransmissionChecksumValidator::slotDownloadChecksumCalculated()
void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray& checksumType,
const QByteArray& checksum)
{
const QByteArray hash = _watcher.future().result();
if( hash != _expectedHash ) {
emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed."));
} else {
// qDebug() << "Checksum checked and matching: " << _expectedHash;
emit validated(hash);
if( checksumType != _expectedChecksumType ) {
emit validationFailed(tr("The checksum header contained an unknown checksum type '%1'").arg(
QString::fromLatin1(_expectedChecksumType)));
return;
}
if( checksum != _expectedChecksum ) {
emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed."));
return;
}
emit validated(checksumType, checksum);
}
}

Просмотреть файл

@ -23,62 +23,84 @@
namespace OCC {
/// Creates a checksum header from type and value.
QByteArray makeChecksumHeader(const QByteArray& checksumType, const QByteArray& checksum);
/// Parses a checksum header
bool parseChecksumHeader(const QByteArray& header, QByteArray* type, QByteArray* checksum);
/// Checks OWNCLOUD_DISABLE_CHECKSUM_UPLOAD
bool uploadChecksumEnabled();
/// Checks OWNCLOUD_DISABLE_CHECKSUM_DOWNLOAD
bool downloadChecksumEnabled();
/**
* @brief The TransmissionChecksumValidator class
* @ingroup libsync
* Computes the checksum of a file.
* \ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT TransmissionChecksumValidator : public QObject
class OWNCLOUDSYNC_EXPORT ComputeChecksum : public QObject
{
Q_OBJECT
public:
explicit TransmissionChecksumValidator(const QString& filePath, QObject *parent = 0);
explicit ComputeChecksum(QObject* parent = 0);
/**
* method to prepare a checksum for transmission and save it to the _checksum
* member of the SyncFileItem *item.
* The kind of requested checksum is taken from config. No need to set from outside.
* Sets the checksum type to be used. The default is empty.
*/
void setChecksumType(const QByteArray& type);
QByteArray checksumType() const;
/**
* Computes the checksum for the given file path.
*
* In any case of processing (checksum set, no checksum required and also unusual error)
* the object will emit the signal validated(). The item->_checksum is than either
* set to a proper value or empty.
* done() is emitted when the calculation finishes.
*/
void uploadValidation();
/**
* method to verify the checksum coming with requests in a checksum header. The required
* checksum method is read from config.
*
* If no checksum is there, or if a correct checksum is there, the signal validated()
* will be emitted. In case of any kind of error, the signal validationFailed() will
* be emitted.
*/
void downloadValidation( const QByteArray& checksumHeader );
/**
* By default the checksum type is read from the config file, but can be overridden
* with this method.
*/
void setChecksumType(const QString& type);
QString checksumType() const;
void start(const QString& filePath);
signals:
void validated(const QByteArray& checksum);
void validationFailed( const QString& errMsg );
void done(const QByteArray& checksumType, const QByteArray& checksum);
private slots:
void slotUploadChecksumCalculated();
void slotDownloadChecksumCalculated();
void slotCalculationDone();
private:
QString _checksumType;
QByteArray _expectedHash;
QByteArray _checksumHeader;
QString _filePath;
QByteArray _checksumType;
// watcher for the checksum calculation thread
QFutureWatcher<QByteArray> _watcher;
};
/**
* Checks whether a file's checksum matches the expected value.
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT ValidateChecksumHeader : public QObject
{
Q_OBJECT
public:
explicit ValidateChecksumHeader(QObject *parent = 0);
/**
* Check a file's actual checksum against the provided checksumHeader
*
* If no checksum is there, or if a correct checksum is there, the signal validated()
* will be emitted. In case of any kind of error, the signal validationFailed() will
* be emitted.
*/
void start(const QString& filePath, const QByteArray& checksumHeader);
signals:
void validated(const QByteArray& checksumType, const QByteArray& checksum);
void validationFailed( const QString& errMsg );
private slots:
void slotChecksumCalculated(const QByteArray& checksumType, const QByteArray& checksum);
private:
QByteArray _expectedChecksumType;
QByteArray _expectedChecksum;
};
}

Просмотреть файл

@ -60,16 +60,63 @@ private slots:
record._remotePerm = "744";
record._mode = -17;
record._fileSize = 213089055;
record._transmissionChecksum = "mychecksum";
record._transmissionChecksumType = "MD5";
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord("foo");
QVERIFY(storedRecord == record);
// Update checksum
record._transmissionChecksum = "newchecksum";
record._transmissionChecksumType = "Adler32";
_db.updateFileRecordChecksum("foo", record._transmissionChecksum, record._transmissionChecksumType);
storedRecord = _db.getFileRecord("foo");
QVERIFY(storedRecord == record);
// Update metadata
record._inode = 12345;
record._modtime = dropMsecs(QDateTime::currentDateTime().addDays(1));
record._type = 7;
record._etag = "789FFF";
record._fileId = "efg";
record._remotePerm = "777";
record._mode = 12;
record._fileSize = 289055;
_db.updateFileRecordMetadata(record);
storedRecord = _db.getFileRecord("foo");
QVERIFY(storedRecord == record);
QVERIFY(_db.deleteFileRecord("foo"));
record = _db.getFileRecord("foo");
QVERIFY(!record.isValid());
}
void testFileRecordChecksum()
{
// Try with and without a checksum
{
SyncJournalFileRecord record;
record._path = "foo-checksum";
record._remotePerm = "744";
record._transmissionChecksum = "mychecksum";
record._transmissionChecksumType = "MD5";
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord("foo-checksum");
QVERIFY(storedRecord == record);
}
{
SyncJournalFileRecord record;
record._path = "foo-nochecksum";
record._remotePerm = "744";
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord("foo-nochecksum");
QVERIFY(storedRecord == record);
}
}
void testDownloadInfo()
{
typedef SyncJournalDb::DownloadInfo Info;

Просмотреть файл

@ -33,14 +33,16 @@ using namespace OCC;
QString _testfile;
QString _expectedError;
QByteArray _expected;
QByteArray _expectedType;
bool _successDown;
bool _errorSeen;
public slots:
void slotUpValidated(const QByteArray& checksum) {
void slotUpValidated(const QByteArray& type, const QByteArray& checksum) {
qDebug() << "Checksum: " << checksum;
QVERIFY(_expected == checksum );
QVERIFY(_expectedType == type );
}
void slotDownValidated() {
@ -62,23 +64,22 @@ using namespace OCC;
rootDir.mkpath(_root );
_testfile = _root+"/csFile";
Utility::writeRandomFile( _testfile);
}
void testUploadChecksummingAdler() {
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this);
vali->setChecksumType("Adler32");
ComputeChecksum *vali = new ComputeChecksum(this);
_expectedType = "Adler32";
vali->setChecksumType(_expectedType);
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray)));
connect(vali, SIGNAL(done(QByteArray,QByteArray)), SLOT(slotUpValidated(QByteArray,QByteArray)));
QString testfile = _testfile;
_expected = "Adler32:"+FileSystem::calcAdler32( testfile );
_expected = FileSystem::calcAdler32( _testfile );
qDebug() << "XX Expected Checksum: " << _expected;
vali->uploadValidation();
vali->start(_testfile);
QEventLoop loop;
connect(vali, SIGNAL(validated(QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
loop.exec();
delete vali;
@ -86,16 +87,16 @@ using namespace OCC;
void testUploadChecksummingMd5() {
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this);
vali->setChecksumType( OCC::checkSumMD5C );
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray)));
ComputeChecksum *vali = new ComputeChecksum(this);
_expectedType = OCC::checkSumMD5C;
vali->setChecksumType(_expectedType);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(slotUpValidated(QByteArray,QByteArray)));
_expected = checkSumMD5C;
_expected.append(":"+FileSystem::calcMd5( _testfile ));
vali->uploadValidation();
_expected = FileSystem::calcMd5( _testfile );
vali->start(_testfile);
QEventLoop loop;
connect(vali, SIGNAL(validated(QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
loop.exec();
delete vali;
@ -103,17 +104,17 @@ using namespace OCC;
void testUploadChecksummingSha1() {
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this);
vali->setChecksumType( OCC::checkSumSHA1C );
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray)));
ComputeChecksum *vali = new ComputeChecksum(this);
_expectedType = OCC::checkSumSHA1C;
vali->setChecksumType(_expectedType);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(slotUpValidated(QByteArray,QByteArray)));
_expected = checkSumSHA1C;
_expected.append(":"+FileSystem::calcSha1( _testfile ));
_expected = FileSystem::calcSha1( _testfile );
vali->uploadValidation();
vali->start(_testfile);
QEventLoop loop;
connect(vali, SIGNAL(validated(QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
loop.exec();
delete vali;
@ -126,22 +127,21 @@ using namespace OCC;
adler.append(FileSystem::calcAdler32( _testfile ));
_successDown = false;
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this);
vali->setChecksumType("Adler32");
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotDownValidated()));
ValidateChecksumHeader *vali = new ValidateChecksumHeader(this);
connect(vali, SIGNAL(validated(QByteArray,QByteArray)), this, SLOT(slotDownValidated()));
connect(vali, SIGNAL(validationFailed(QString)), this, SLOT(slotDownError(QString)));
vali->downloadValidation(adler);
vali->start(_testfile, adler);
QTRY_VERIFY(_successDown);
_expectedError = QLatin1String("The downloaded file does not match the checksum, it will be resumed.");
_errorSeen = false;
vali->downloadValidation("Adler32:543345");
vali->start(_testfile, "Adler32:543345");
QTRY_VERIFY(_errorSeen);
_expectedError = QLatin1String("The checksum header is malformed.");
_expectedError = QLatin1String("The checksum header contained an unknown checksum type 'Klaas32'");
_errorSeen = false;
vali->downloadValidation("Klaas32:543345");
vali->start(_testfile, "Klaas32:543345");
QTRY_VERIFY(_errorSeen);
delete vali;