Merge pull request #3669 from nextcloud/bugfix/variousFixesV2

Bugfix/various fixes v2
This commit is contained in:
Matthieu Gallien 2021-08-23 10:40:32 +02:00 committed by GitHub
commit 138323b79d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1151 additions and 652 deletions

@ -1 +1 @@
Subproject commit 34b4665bd7ae5a86115f366b3ec4935d7ecb769f
Subproject commit 5423c0ef54f99cea2628c2cb3f48e39e215f9961

View File

@ -505,6 +505,9 @@ restart_sync:
selectiveSyncFixup(&db, selectiveSyncList);
}
SyncOptions opt;
opt.fillFromEnvironmentVariables();
opt.verifyChunkSizes();
SyncEngine engine(account, options.source_dir, folder, &db);
engine.setIgnoreHiddenFiles(options.ignoreHiddenFiles);
engine.setNetworkLimits(options.uplimit, options.downlimit);

View File

@ -144,6 +144,9 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &
QByteArray findBestChecksum(const QByteArray &_checksums)
{
if (_checksums.isEmpty()) {
return {};
}
const auto checksums = QString::fromUtf8(_checksums);
int i = 0;
// The order of the searches here defines the preference ordering.
@ -162,7 +165,7 @@ QByteArray findBestChecksum(const QByteArray &_checksums)
return _checksums.mid(i, end - i);
}
qCWarning(lcChecksums) << "Failed to parse" << _checksums;
return QByteArray();
return {};
}
bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum)

View File

@ -5,6 +5,7 @@ set(common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/checksums.cpp
${CMAKE_CURRENT_LIST_DIR}/filesystembase.cpp
${CMAKE_CURRENT_LIST_DIR}/ownsql.cpp
${CMAKE_CURRENT_LIST_DIR}/preparedsqlquerymanager.cpp
${CMAKE_CURRENT_LIST_DIR}/syncjournaldb.cpp
${CMAKE_CURRENT_LIST_DIR}/syncjournalfilerecord.cpp
${CMAKE_CURRENT_LIST_DIR}/utility.cpp

View File

@ -490,28 +490,4 @@ void SqlQuery::reset_and_clear_bindings()
}
}
PreparedSqlQueryRAII::PreparedSqlQueryRAII(SqlQuery *query)
: _query(query)
{
Q_ASSERT(!sqlite3_stmt_busy(_query->_stmt));
}
PreparedSqlQueryRAII::PreparedSqlQueryRAII(SqlQuery *query, const QByteArray &sql, SqlDatabase &db)
: _query(query)
{
Q_ASSERT(!sqlite3_stmt_busy(_query->_stmt));
ENFORCE(!query->_sqldb || &db == query->_sqldb)
query->_sqldb = &db;
query->_db = db.sqliteDb();
if (!query->_stmt) {
_ok = query->prepare(sql) == 0;
}
}
PreparedSqlQueryRAII::~PreparedSqlQueryRAII()
{
_query->reset_and_clear_bindings();
}
} // namespace OCC

View File

@ -168,42 +168,9 @@ private:
QByteArray _sql;
friend class SqlDatabase;
friend class PreparedSqlQueryRAII;
friend class PreparedSqlQueryManager;
};
class OCSYNC_EXPORT PreparedSqlQueryRAII
{
public:
/**
* Simple Guard which allow reuse of prepared querys.
* The queries are reset in the destructor to prevent wal locks
*/
PreparedSqlQueryRAII(SqlQuery *query);
/**
* Prepare the SqlQuery if it was not prepared yet.
*/
PreparedSqlQueryRAII(SqlQuery *query, const QByteArray &sql, SqlDatabase &db);
~PreparedSqlQueryRAII();
explicit operator bool() const { return _ok; }
SqlQuery *operator->() const
{
Q_ASSERT(_ok);
return _query;
}
SqlQuery &operator*() const &
{
Q_ASSERT(_ok);
return *_query;
}
private:
SqlQuery *const _query;
bool _ok = true;
Q_DISABLE_COPY(PreparedSqlQueryRAII);
};
} // namespace OCC
#endif // OWNSQL_H

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) by Hannah von Reth <hannah.vonreth@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "preparedsqlquerymanager.h"
#include <sqlite3.h>
using namespace OCC;
PreparedSqlQuery::PreparedSqlQuery(SqlQuery *query, bool ok)
: _query(query)
, _ok(ok)
{
}
PreparedSqlQuery::~PreparedSqlQuery()
{
_query->reset_and_clear_bindings();
}
const PreparedSqlQuery PreparedSqlQueryManager::get(PreparedSqlQueryManager::Key key)
{
auto &query = _queries[key];
ENFORCE(query._stmt)
Q_ASSERT(!sqlite3_stmt_busy(query._stmt));
return { &query };
}
const PreparedSqlQuery PreparedSqlQueryManager::get(PreparedSqlQueryManager::Key key, const QByteArray &sql, SqlDatabase &db)
{
auto &query = _queries[key];
Q_ASSERT(!sqlite3_stmt_busy(query._stmt));
ENFORCE(!query._sqldb || &db == query._sqldb)
if (!query._stmt) {
query._sqldb = &db;
query._db = db.sqliteDb();
return { &query, query.prepare(sql) == 0 };
}
return { &query };
}

View File

@ -0,0 +1,119 @@
/*
* Copyright (C) by Hannah von Reth <hannah.vonreth@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include "ocsynclib.h"
#include "ownsql.h"
#include "common/asserts.h"
namespace OCC {
class OCSYNC_EXPORT PreparedSqlQuery
{
public:
~PreparedSqlQuery();
explicit operator bool() const { return _ok; }
SqlQuery *operator->() const
{
Q_ASSERT(_ok);
return _query;
}
SqlQuery &operator*() const &
{
Q_ASSERT(_ok);
return *_query;
}
private:
PreparedSqlQuery(SqlQuery *query, bool ok = true);
SqlQuery *_query;
bool _ok;
friend class PreparedSqlQueryManager;
};
/**
* @brief Manage PreparedSqlQuery
*/
class OCSYNC_EXPORT PreparedSqlQueryManager
{
public:
enum Key {
GetFileRecordQuery,
GetFileRecordQueryByMangledName,
GetFileRecordQueryByInode,
GetFileRecordQueryByFileId,
GetFilesBelowPathQuery,
GetAllFilesQuery,
ListFilesInPathQuery,
SetFileRecordQuery,
SetFileRecordChecksumQuery,
SetFileRecordLocalMetadataQuery,
GetDownloadInfoQuery,
SetDownloadInfoQuery,
DeleteDownloadInfoQuery,
GetUploadInfoQuery,
SetUploadInfoQuery,
DeleteUploadInfoQuery,
DeleteFileRecordPhash,
DeleteFileRecordRecursively,
GetErrorBlacklistQuery,
SetErrorBlacklistQuery,
GetSelectiveSyncListQuery,
GetChecksumTypeIdQuery,
GetChecksumTypeQuery,
InsertChecksumTypeQuery,
GetDataFingerprintQuery,
SetDataFingerprintQuery1,
SetDataFingerprintQuery2,
SetKeyValueStoreQuery,
GetKeyValueStoreQuery,
DeleteKeyValueStoreQuery,
GetConflictRecordQuery,
SetConflictRecordQuery,
DeleteConflictRecordQuery,
GetRawPinStateQuery,
GetEffectivePinStateQuery,
GetSubPinsQuery,
CountDehydratedFilesQuery,
SetPinStateQuery,
WipePinStateQuery,
PreparedQueryCount
};
PreparedSqlQueryManager() = default;
/**
* The queries are reset in the destructor to prevent wal locks
*/
const PreparedSqlQuery get(Key key);
/**
* Prepare the SqlQuery if it was not prepared yet.
*/
const PreparedSqlQuery get(Key key, const QByteArray &sql, SqlDatabase &db);
private:
SqlQuery _queries[PreparedQueryCount];
Q_DISABLE_COPY(PreparedSqlQueryManager);
};
}

View File

@ -104,6 +104,7 @@ public:
ASSERT(!_isError);
return _result;
}
T operator*() &&
{
ASSERT(!_isError);
@ -116,6 +117,12 @@ public:
return &_result;
}
const T &get() const
{
ASSERT(!_isError)
return _result;
}
const Error &error() const &
{
ASSERT(_isError);

View File

@ -31,6 +31,7 @@
#include "filesystembase.h"
#include "common/asserts.h"
#include "common/checksums.h"
#include "common/preparedsqlquerymanager.h"
#include "common/c_jhash.h"
@ -586,16 +587,15 @@ bool SyncJournalDb::checkConnect()
if (forceRemoteDiscovery) {
forceRemoteDiscoveryNextSyncLocked();
}
const PreparedSqlQueryRAII deleteDownloadInfo(&_deleteDownloadInfoQuery, QByteArrayLiteral("DELETE FROM downloadinfo WHERE path=?1"), _db);
const auto deleteDownloadInfo = _queryManager.get(PreparedSqlQueryManager::DeleteDownloadInfoQuery, QByteArrayLiteral("DELETE FROM downloadinfo WHERE path=?1"), _db);
if (!deleteDownloadInfo) {
return sqlFail(QStringLiteral("prepare _deleteDownloadInfoQuery"), _deleteDownloadInfoQuery);
return sqlFail(QStringLiteral("prepare _deleteDownloadInfoQuery"), *deleteDownloadInfo);
}
const PreparedSqlQueryRAII deleteUploadInfoQuery(&_deleteUploadInfoQuery, QByteArrayLiteral("DELETE FROM uploadinfo WHERE path=?1"), _db);
const auto deleteUploadInfoQuery = _queryManager.get(PreparedSqlQueryManager::DeleteUploadInfoQuery, QByteArrayLiteral("DELETE FROM uploadinfo WHERE path=?1"), _db);
if (!deleteUploadInfoQuery) {
return sqlFail(QStringLiteral("prepare _deleteUploadInfoQuery"), _deleteUploadInfoQuery);
return sqlFail(QStringLiteral("prepare _deleteUploadInfoQuery"), *deleteUploadInfoQuery);
}
QByteArray sql("SELECT lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory, requestId "
@ -605,7 +605,7 @@ bool SyncJournalDb::checkConnect()
// case insensitively
sql += " COLLATE NOCASE";
}
const PreparedSqlQueryRAII getErrorBlacklistQuery(&_getErrorBlacklistQuery, sql, _db);
const auto getErrorBlacklistQuery = _queryManager.get(PreparedSqlQueryManager::GetErrorBlacklistQuery, sql, _db);
if (!getErrorBlacklistQuery) {
return sqlFail(QStringLiteral("prepare _getErrorBlacklistQuery"), *getErrorBlacklistQuery);
}
@ -892,7 +892,7 @@ QVector<QByteArray> SyncJournalDb::tableColumns(const QByteArray &table)
qint64 SyncJournalDb::getPHash(const QByteArray &file)
{
int64_t h = 0;
qint64 h = 0;
int len = file.length();
h = c_jhash64((uint8_t *)file.data(), len, 0);
@ -922,7 +922,7 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
<< "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader
<< "e2eMangledName:" << record.e2eMangledName() << "isE2eEncrypted:" << record._isE2eEncrypted;
qlonglong phash = getPHash(record._path);
const qint64 phash = getPHash(record._path);
if (checkConnect()) {
int plen = record._path.length();
@ -937,10 +937,10 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
parseChecksumHeader(record._checksumHeader, &checksumType, &checksum);
int contentChecksumTypeId = mapChecksumType(checksumType);
const PreparedSqlQueryRAII query(&_setFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18);"),
_db);
const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18);"),
_db);
if (!query) {
return query->error();
}
@ -985,7 +985,7 @@ void SyncJournalDb::keyValueStoreSet(const QString &key, QVariant value)
return;
}
const PreparedSqlQueryRAII query(&_setKeyValueStoreQuery, QByteArrayLiteral("INSERT OR REPLACE INTO key_value_store (key, value) VALUES(?1, ?2);"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::SetKeyValueStoreQuery, QByteArrayLiteral("INSERT OR REPLACE INTO key_value_store (key, value) VALUES(?1, ?2);"), _db);
if (!query) {
return;
}
@ -1002,7 +1002,7 @@ qint64 SyncJournalDb::keyValueStoreGetInt(const QString &key, qint64 defaultValu
return defaultValue;
}
const PreparedSqlQueryRAII query(&_getKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key = ?1;"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key = ?1;"), _db);
if (!query) {
return defaultValue;
}
@ -1024,7 +1024,7 @@ QVariant SyncJournalDb::keyValueStoreGet(const QString &key, QVariant defaultVal
return defaultValue;
}
const PreparedSqlQueryRAII query(&_getKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key = ?1;"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key = ?1;"), _db);
if (!query) {
return defaultValue;
}
@ -1041,7 +1041,7 @@ QVariant SyncJournalDb::keyValueStoreGet(const QString &key, QVariant defaultVal
void SyncJournalDb::keyValueStoreDelete(const QString &key)
{
const PreparedSqlQueryRAII query(&_deleteKeyValueStoreQuery, QByteArrayLiteral("DELETE FROM key_value_store WHERE key=?1;"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteKeyValueStoreQuery, QByteArrayLiteral("DELETE FROM key_value_store WHERE key=?1;"), _db);
if (!query) {
qCWarning(lcDb) << "Failed to initOrReset _deleteKeyValueStoreQuery";
Q_ASSERT(false);
@ -1063,12 +1063,12 @@ bool SyncJournalDb::deleteFileRecord(const QString &filename, bool recursively)
// always delete the actual file.
{
const PreparedSqlQueryRAII query(&_deleteFileRecordPhash, QByteArrayLiteral("DELETE FROM metadata WHERE phash=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteFileRecordPhash, QByteArrayLiteral("DELETE FROM metadata WHERE phash=?1"), _db);
if (!query) {
return false;
}
qlonglong phash = getPHash(filename.toUtf8());
const qint64 phash = getPHash(filename.toUtf8());
query->bindValue(1, phash);
if (!query->exec()) {
@ -1077,7 +1077,7 @@ bool SyncJournalDb::deleteFileRecord(const QString &filename, bool recursively)
}
if (recursively) {
const PreparedSqlQueryRAII query(&_deleteFileRecordRecursively, QByteArrayLiteral("DELETE FROM metadata WHERE " IS_PREFIX_PATH_OF("?1", "path")), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteFileRecordRecursively, QByteArrayLiteral("DELETE FROM metadata WHERE " IS_PREFIX_PATH_OF("?1", "path")), _db);
if (!query)
return false;
query->bindValue(1, filename);
@ -1109,7 +1109,7 @@ bool SyncJournalDb::getFileRecord(const QByteArray &filename, SyncJournalFileRec
return false;
if (!filename.isEmpty()) {
const PreparedSqlQueryRAII query(&_getFileRecordQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE phash=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE phash=?1"), _db);
if (!query) {
return false;
}
@ -1153,7 +1153,7 @@ bool SyncJournalDb::getFileRecordByE2eMangledName(const QString &mangledName, Sy
}
if (!mangledName.isEmpty()) {
const PreparedSqlQueryRAII query(&_getFileRecordQueryByMangledName, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE e2eMangledName=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByMangledName, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE e2eMangledName=?1"), _db);
if (!query) {
return false;
}
@ -1193,7 +1193,7 @@ bool SyncJournalDb::getFileRecordByInode(quint64 inode, SyncJournalFileRecord *r
if (!checkConnect())
return false;
const PreparedSqlQueryRAII query(&_getFileRecordQueryByInode, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE inode=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByInode, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE inode=?1"), _db);
if (!query)
return false;
@ -1221,7 +1221,7 @@ bool SyncJournalDb::getFileRecordsByFileId(const QByteArray &fileId, const std::
if (!checkConnect())
return false;
const PreparedSqlQueryRAII query(&_getFileRecordQueryByFileId, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE fileid=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByFileId, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE fileid=?1"), _db);
if (!query) {
return false;
}
@ -1281,7 +1281,7 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio
// and find nothing. So, unfortunately, we have to use a different query for
// retrieving the whole tree.
const PreparedSqlQueryRAII query(&_getAllFilesQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " ORDER BY path||'/' ASC"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetAllFilesQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " ORDER BY path||'/' ASC"), _db);
if (!query) {
return false;
}
@ -1289,15 +1289,15 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio
} else {
// This query is used to skip discovery and fill the tree from the
// database instead
const PreparedSqlQueryRAII query(&_getFilesBelowPathQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE " IS_PREFIX_PATH_OF("?1", "path")
" OR " IS_PREFIX_PATH_OF("?1", "e2eMangledName")
// We want to ensure that the contents of a directory are sorted
// directly behind the directory itself. Without this ORDER BY
// an ordering like foo, foo-2, foo/file would be returned.
// With the trailing /, we get foo-2, foo, foo/file. This property
// is used in fill_tree_from_db().
" ORDER BY path||'/' ASC"),
_db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetFilesBelowPathQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE " IS_PREFIX_PATH_OF("?1", "path")
" OR " IS_PREFIX_PATH_OF("?1", "e2eMangledName")
// We want to ensure that the contents of a directory are sorted
// directly behind the directory itself. Without this ORDER BY
// an ordering like foo, foo-2, foo/file would be returned.
// With the trailing /, we get foo-2, foo, foo/file. This property
// is used in fill_tree_from_db().
" ORDER BY path||'/' ASC"),
_db);
if (!query) {
return false;
}
@ -1317,7 +1317,7 @@ bool SyncJournalDb::listFilesInPath(const QByteArray& path,
if (!checkConnect())
return false;
const PreparedSqlQueryRAII query(&_listFilesInPathQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE parent_hash(path) = ?1 ORDER BY path||'/' ASC"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::ListFilesInPathQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE parent_hash(path) = ?1 ORDER BY path||'/' ASC"), _db);
if (!query) {
return false;
}
@ -1372,7 +1372,7 @@ bool SyncJournalDb::updateFileRecordChecksum(const QString &filename,
qCInfo(lcDb) << "Updating file checksum" << filename << contentChecksum << contentChecksumType;
qlonglong phash = getPHash(filename.toUtf8());
const qint64 phash = getPHash(filename.toUtf8());
if (!checkConnect()) {
qCWarning(lcDb) << "Failed to connect database.";
return false;
@ -1380,9 +1380,9 @@ bool SyncJournalDb::updateFileRecordChecksum(const QString &filename,
int checksumTypeId = mapChecksumType(contentChecksumType);
const PreparedSqlQueryRAII query(&_setFileRecordChecksumQuery, QByteArrayLiteral("UPDATE metadata"
" SET contentChecksum = ?2, contentChecksumTypeId = ?3"
" WHERE phash == ?1;"),
const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordChecksumQuery, QByteArrayLiteral("UPDATE metadata"
" SET contentChecksum = ?2, contentChecksumTypeId = ?3"
" WHERE phash == ?1;"),
_db);
if (!query) {
return false;
@ -1401,15 +1401,15 @@ bool SyncJournalDb::updateLocalMetadata(const QString &filename,
qCInfo(lcDb) << "Updating local metadata for:" << filename << modtime << size << inode;
qlonglong phash = getPHash(filename.toUtf8());
const qint64 phash = getPHash(filename.toUtf8());
if (!checkConnect()) {
qCWarning(lcDb) << "Failed to connect database.";
return false;
}
const PreparedSqlQueryRAII query(&_setFileRecordLocalMetadataQuery, QByteArrayLiteral("UPDATE metadata"
" SET inode=?2, modtime=?3, filesize=?4"
" WHERE phash == ?1;"),
const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordLocalMetadataQuery, QByteArrayLiteral("UPDATE metadata"
" SET inode=?2, modtime=?3, filesize=?4"
" WHERE phash == ?1;"),
_db);
if (!query) {
return false;
@ -1428,8 +1428,8 @@ Optional<SyncJournalDb::HasHydratedDehydrated> SyncJournalDb::hasHydratedOrDehyd
if (!checkConnect())
return {};
const PreparedSqlQueryRAII query(&_countDehydratedFilesQuery, QByteArrayLiteral("SELECT DISTINCT type FROM metadata"
" WHERE (" IS_PREFIX_PATH_OR_EQUAL("?1", "path") " OR ?1 == '');"),
const auto query = _queryManager.get(PreparedSqlQueryManager::CountDehydratedFilesQuery, QByteArrayLiteral("SELECT DISTINCT type FROM metadata"
" WHERE (" IS_PREFIX_PATH_OR_EQUAL("?1", "path") " OR ?1 == '');"),
_db);
if (!query) {
return {};
@ -1490,7 +1490,7 @@ SyncJournalDb::DownloadInfo SyncJournalDb::getDownloadInfo(const QString &file)
DownloadInfo res;
if (checkConnect()) {
const PreparedSqlQueryRAII query(&_getDownloadInfoQuery, QByteArrayLiteral("SELECT tmpfile, etag, errorcount FROM downloadinfo WHERE path=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetDownloadInfoQuery, QByteArrayLiteral("SELECT tmpfile, etag, errorcount FROM downloadinfo WHERE path=?1"), _db);
if (!query) {
return res;
}
@ -1518,9 +1518,9 @@ void SyncJournalDb::setDownloadInfo(const QString &file, const SyncJournalDb::Do
if (i._valid) {
const PreparedSqlQueryRAII query(&_setDownloadInfoQuery, QByteArrayLiteral("INSERT OR REPLACE INTO downloadinfo "
"(path, tmpfile, etag, errorcount) "
"VALUES ( ?1 , ?2, ?3, ?4 )"),
const auto query = _queryManager.get(PreparedSqlQueryManager::SetDownloadInfoQuery, QByteArrayLiteral("INSERT OR REPLACE INTO downloadinfo "
"(path, tmpfile, etag, errorcount) "
"VALUES ( ?1 , ?2, ?3, ?4 )"),
_db);
if (!query) {
return;
@ -1531,7 +1531,7 @@ void SyncJournalDb::setDownloadInfo(const QString &file, const SyncJournalDb::Do
query->bindValue(4, i._errorCount);
query->exec();
} else {
const PreparedSqlQueryRAII query(&_deleteDownloadInfoQuery);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteDownloadInfoQuery);
query->bindValue(1, file);
query->exec();
}
@ -1568,7 +1568,7 @@ QVector<SyncJournalDb::DownloadInfo> SyncJournalDb::getAndDeleteStaleDownloadInf
}
{
const PreparedSqlQueryRAII query(&_deleteDownloadInfoQuery);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteDownloadInfoQuery);
if (!deleteBatch(*query, superfluousPaths, QStringLiteral("downloadinfo"))) {
return empty_result;
}
@ -1602,8 +1602,8 @@ SyncJournalDb::UploadInfo SyncJournalDb::getUploadInfo(const QString &file)
UploadInfo res;
if (checkConnect()) {
const PreparedSqlQueryRAII query(&_getUploadInfoQuery, QByteArrayLiteral("SELECT chunk, transferid, errorcount, size, modtime, contentChecksum FROM "
"uploadinfo WHERE path=?1"),
const auto query = _queryManager.get(PreparedSqlQueryManager::GetUploadInfoQuery, QByteArrayLiteral("SELECT chunk, transferid, errorcount, size, modtime, contentChecksum FROM "
"uploadinfo WHERE path=?1"),
_db);
if (!query) {
return res;
@ -1637,9 +1637,9 @@ void SyncJournalDb::setUploadInfo(const QString &file, const SyncJournalDb::Uplo
}
if (i._valid) {
const PreparedSqlQueryRAII query(&_setUploadInfoQuery, QByteArrayLiteral("INSERT OR REPLACE INTO uploadinfo "
"(path, chunk, transferid, errorcount, size, modtime, contentChecksum) "
"VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 , ?7 )"),
const auto query = _queryManager.get(PreparedSqlQueryManager::SetUploadInfoQuery, QByteArrayLiteral("INSERT OR REPLACE INTO uploadinfo "
"(path, chunk, transferid, errorcount, size, modtime, contentChecksum) "
"VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 , ?7 )"),
_db);
if (!query) {
return;
@ -1657,7 +1657,7 @@ void SyncJournalDb::setUploadInfo(const QString &file, const SyncJournalDb::Uplo
return;
}
} else {
const PreparedSqlQueryRAII query(&_deleteUploadInfoQuery);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteUploadInfoQuery);
query->bindValue(1, file);
if (!query->exec()) {
@ -1692,7 +1692,7 @@ QVector<uint> SyncJournalDb::deleteStaleUploadInfos(const QSet<QString> &keep)
}
}
const PreparedSqlQueryRAII deleteUploadInfoQuery(&_deleteUploadInfoQuery);
const auto deleteUploadInfoQuery = _queryManager.get(PreparedSqlQueryManager::DeleteUploadInfoQuery);
deleteBatch(*deleteUploadInfoQuery, superfluousPaths, QStringLiteral("uploadinfo"));
return ids;
}
@ -1706,7 +1706,7 @@ SyncJournalErrorBlacklistRecord SyncJournalDb::errorBlacklistEntry(const QString
return entry;
if (checkConnect()) {
const PreparedSqlQueryRAII query(&_getErrorBlacklistQuery);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetErrorBlacklistQuery);
query->bindValue(1, file);
if (query->exec()) {
if (query->next().hasData) {
@ -1847,9 +1847,9 @@ void SyncJournalDb::setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord
return;
}
const PreparedSqlQueryRAII query(&_setErrorBlacklistQuery, QByteArrayLiteral("INSERT OR REPLACE INTO blacklist "
"(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory, requestId) "
"VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"),
const auto query = _queryManager.get(PreparedSqlQueryManager::SetErrorBlacklistQuery, QByteArrayLiteral("INSERT OR REPLACE INTO blacklist "
"(path, lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory, requestId) "
"VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"),
_db);
if (!query) {
return;
@ -1927,7 +1927,7 @@ QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncList
return result;
}
const PreparedSqlQueryRAII query(&_getSelectiveSyncListQuery, QByteArrayLiteral("SELECT path FROM selectivesync WHERE type=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetSelectiveSyncListQuery, QByteArrayLiteral("SELECT path FROM selectivesync WHERE type=?1"), _db);
if (!query) {
*ok = false;
return result;
@ -2064,7 +2064,7 @@ QByteArray SyncJournalDb::getChecksumType(int checksumTypeId)
}
// Retrieve the id
const PreparedSqlQueryRAII query(&_getChecksumTypeQuery, QByteArrayLiteral("SELECT name FROM checksumtype WHERE id=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetChecksumTypeQuery, QByteArrayLiteral("SELECT name FROM checksumtype WHERE id=?1"), _db);
if (!query) {
return {};
}
@ -2092,7 +2092,7 @@ int SyncJournalDb::mapChecksumType(const QByteArray &checksumType)
// Ensure the checksum type is in the db
{
const PreparedSqlQueryRAII query(&_insertChecksumTypeQuery, QByteArrayLiteral("INSERT OR IGNORE INTO checksumtype (name) VALUES (?1)"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::InsertChecksumTypeQuery, QByteArrayLiteral("INSERT OR IGNORE INTO checksumtype (name) VALUES (?1)"), _db);
if (!query) {
return 0;
}
@ -2104,7 +2104,7 @@ int SyncJournalDb::mapChecksumType(const QByteArray &checksumType)
// Retrieve the id
{
const PreparedSqlQueryRAII query(&_getChecksumTypeIdQuery, QByteArrayLiteral("SELECT id FROM checksumtype WHERE name=?1"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetChecksumTypeIdQuery, QByteArrayLiteral("SELECT id FROM checksumtype WHERE name=?1"), _db);
if (!query) {
return 0;
}
@ -2130,7 +2130,7 @@ QByteArray SyncJournalDb::dataFingerprint()
return QByteArray();
}
const PreparedSqlQueryRAII query(&_getDataFingerprintQuery, QByteArrayLiteral("SELECT fingerprint FROM datafingerprint"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetDataFingerprintQuery, QByteArrayLiteral("SELECT fingerprint FROM datafingerprint"), _db);
if (!query) {
return QByteArray();
}
@ -2152,16 +2152,16 @@ void SyncJournalDb::setDataFingerprint(const QByteArray &dataFingerprint)
return;
}
const PreparedSqlQueryRAII setDataFingerprintQuery1(&_setDataFingerprintQuery1, QByteArrayLiteral("DELETE FROM datafingerprint;"), _db);
const PreparedSqlQueryRAII setDataFingerprintQuery2(&_setDataFingerprintQuery2, QByteArrayLiteral("INSERT INTO datafingerprint (fingerprint) VALUES (?1);"), _db);
const auto setDataFingerprintQuery1 = _queryManager.get(PreparedSqlQueryManager::SetDataFingerprintQuery1, QByteArrayLiteral("DELETE FROM datafingerprint;"), _db);
const auto setDataFingerprintQuery2 = _queryManager.get(PreparedSqlQueryManager::SetDataFingerprintQuery2, QByteArrayLiteral("INSERT INTO datafingerprint (fingerprint) VALUES (?1);"), _db);
if (!setDataFingerprintQuery1 || !setDataFingerprintQuery2) {
return;
}
_setDataFingerprintQuery1.exec();
setDataFingerprintQuery1->exec();
_setDataFingerprintQuery2.bindValue(1, dataFingerprint);
_setDataFingerprintQuery2.exec();
setDataFingerprintQuery2->bindValue(1, dataFingerprint);
setDataFingerprintQuery2->exec();
}
void SyncJournalDb::setConflictRecord(const ConflictRecord &record)
@ -2170,10 +2170,10 @@ void SyncJournalDb::setConflictRecord(const ConflictRecord &record)
if (!checkConnect())
return;
const PreparedSqlQueryRAII query(&_setConflictRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO conflicts "
"(path, baseFileId, baseModtime, baseEtag, basePath) "
"VALUES (?1, ?2, ?3, ?4, ?5);"),
_db);
const auto query = _queryManager.get(PreparedSqlQueryManager::SetConflictRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO conflicts "
"(path, baseFileId, baseModtime, baseEtag, basePath) "
"VALUES (?1, ?2, ?3, ?4, ?5);"),
_db);
ASSERT(query)
query->bindValue(1, record.path);
query->bindValue(2, record.baseFileId);
@ -2191,7 +2191,7 @@ ConflictRecord SyncJournalDb::conflictRecord(const QByteArray &path)
if (!checkConnect()) {
return entry;
}
const PreparedSqlQueryRAII query(&_getConflictRecordQuery, QByteArrayLiteral("SELECT baseFileId, baseModtime, baseEtag, basePath FROM conflicts WHERE path=?1;"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::GetConflictRecordQuery, QByteArrayLiteral("SELECT baseFileId, baseModtime, baseEtag, basePath FROM conflicts WHERE path=?1;"), _db);
ASSERT(query)
query->bindValue(1, path);
ASSERT(query->exec())
@ -2212,7 +2212,7 @@ void SyncJournalDb::deleteConflictRecord(const QByteArray &path)
if (!checkConnect())
return;
const PreparedSqlQueryRAII query(&_deleteConflictRecordQuery, QByteArrayLiteral("DELETE FROM conflicts WHERE path=?1;"), _db);
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteConflictRecordQuery, QByteArrayLiteral("DELETE FROM conflicts WHERE path=?1;"), _db);
ASSERT(query)
query->bindValue(1, path);
ASSERT(query->exec())
@ -2288,7 +2288,7 @@ Optional<PinState> SyncJournalDb::PinStateInterface::rawForPath(const QByteArray
if (!_db->checkConnect())
return {};
const PreparedSqlQueryRAII query(&_db->_getRawPinStateQuery, QByteArrayLiteral("SELECT pinState FROM flags WHERE path == ?1;"), _db->_db);
const auto query = _db->_queryManager.get(PreparedSqlQueryManager::GetRawPinStateQuery, QByteArrayLiteral("SELECT pinState FROM flags WHERE path == ?1;"), _db->_db);
ASSERT(query)
query->bindValue(1, path);
query->exec();
@ -2309,14 +2309,13 @@ Optional<PinState> SyncJournalDb::PinStateInterface::effectiveForPath(const QByt
if (!_db->checkConnect())
return {};
const PreparedSqlQueryRAII query(&_db->_getEffectivePinStateQuery, QByteArrayLiteral(
"SELECT pinState FROM flags WHERE"
// explicitly allow "" to represent the root path
// (it'd be great if paths started with a / and "/" could be the root)
" (" IS_PREFIX_PATH_OR_EQUAL("path", "?1") " OR path == '')"
" AND pinState is not null AND pinState != 0"
" ORDER BY length(path) DESC LIMIT 1;"),
_db->_db);
const auto query = _db->_queryManager.get(PreparedSqlQueryManager::GetEffectivePinStateQuery, QByteArrayLiteral("SELECT pinState FROM flags WHERE"
// explicitly allow "" to represent the root path
// (it'd be great if paths started with a / and "/" could be the root)
" (" IS_PREFIX_PATH_OR_EQUAL("path", "?1") " OR path == '')"
" AND pinState is not null AND pinState != 0"
" ORDER BY length(path) DESC LIMIT 1;"),
_db->_db);
ASSERT(query)
query->bindValue(1, path);
query->exec();
@ -2344,10 +2343,10 @@ Optional<PinState> SyncJournalDb::PinStateInterface::effectiveForPathRecursive(c
return {};
// Find all the non-inherited pin states below the item
const PreparedSqlQueryRAII query(&_db->_getSubPinsQuery, QByteArrayLiteral("SELECT DISTINCT pinState FROM flags WHERE"
" (" IS_PREFIX_PATH_OF("?1", "path") " OR ?1 == '')"
" AND pinState is not null and pinState != 0;"),
_db->_db);
const auto query = _db->_queryManager.get(PreparedSqlQueryManager::GetSubPinsQuery, QByteArrayLiteral("SELECT DISTINCT pinState FROM flags WHERE"
" (" IS_PREFIX_PATH_OF("?1", "path") " OR ?1 == '')"
" AND pinState is not null and pinState != 0;"),
_db->_db);
ASSERT(query)
query->bindValue(1, path);
query->exec();
@ -2373,13 +2372,13 @@ void SyncJournalDb::PinStateInterface::setForPath(const QByteArray &path, PinSta
if (!_db->checkConnect())
return;
const PreparedSqlQueryRAII query(&_db->_setPinStateQuery, QByteArrayLiteral(
// If we had sqlite >=3.24.0 everywhere this could be an upsert,
// making further flags columns easy
//"INSERT INTO flags(path, pinState) VALUES(?1, ?2)"
//" ON CONFLICT(path) DO UPDATE SET pinState=?2;"),
// Simple version that doesn't work nicely with multiple columns:
"INSERT OR REPLACE INTO flags(path, pinState) VALUES(?1, ?2);"),
const auto query = _db->_queryManager.get(PreparedSqlQueryManager::SetPinStateQuery, QByteArrayLiteral(
// If we had sqlite >=3.24.0 everywhere this could be an upsert,
// making further flags columns easy
//"INSERT INTO flags(path, pinState) VALUES(?1, ?2)"
//" ON CONFLICT(path) DO UPDATE SET pinState=?2;"),
// Simple version that doesn't work nicely with multiple columns:
"INSERT OR REPLACE INTO flags(path, pinState) VALUES(?1, ?2);"),
_db->_db);
ASSERT(query)
query->bindValue(1, path);
@ -2393,10 +2392,10 @@ void SyncJournalDb::PinStateInterface::wipeForPathAndBelow(const QByteArray &pat
if (!_db->checkConnect())
return;
const PreparedSqlQueryRAII query(&_db->_wipePinStateQuery, QByteArrayLiteral("DELETE FROM flags WHERE "
// Allow "" to delete everything
" (" IS_PREFIX_PATH_OR_EQUAL("?1", "path") " OR ?1 == '');"),
_db->_db);
const auto query = _db->_queryManager.get(PreparedSqlQueryManager::WipePinStateQuery, QByteArrayLiteral("DELETE FROM flags WHERE "
// Allow "" to delete everything
" (" IS_PREFIX_PATH_OR_EQUAL("?1", "path") " OR ?1 == '');"),
_db->_db);
ASSERT(query)
query->bindValue(1, path);
query->exec();

View File

@ -28,6 +28,7 @@
#include "common/utility.h"
#include "common/ownsql.h"
#include "common/preparedsqlquerymanager.h"
#include "common/syncjournalfilerecord.h"
#include "common/result.h"
#include "common/pinstate.h"
@ -397,46 +398,6 @@ private:
int _transaction;
bool _metadataTableIsEmpty;
SqlQuery _getFileRecordQuery;
SqlQuery _getFileRecordQueryByMangledName;
SqlQuery _getFileRecordQueryByInode;
SqlQuery _getFileRecordQueryByFileId;
SqlQuery _getFilesBelowPathQuery;
SqlQuery _getAllFilesQuery;
SqlQuery _listFilesInPathQuery;
SqlQuery _setFileRecordQuery;
SqlQuery _setFileRecordChecksumQuery;
SqlQuery _setFileRecordLocalMetadataQuery;
SqlQuery _getDownloadInfoQuery;
SqlQuery _setDownloadInfoQuery;
SqlQuery _deleteDownloadInfoQuery;
SqlQuery _getUploadInfoQuery;
SqlQuery _setUploadInfoQuery;
SqlQuery _deleteUploadInfoQuery;
SqlQuery _deleteFileRecordPhash;
SqlQuery _deleteFileRecordRecursively;
SqlQuery _getErrorBlacklistQuery;
SqlQuery _setErrorBlacklistQuery;
SqlQuery _getSelectiveSyncListQuery;
SqlQuery _getChecksumTypeIdQuery;
SqlQuery _getChecksumTypeQuery;
SqlQuery _insertChecksumTypeQuery;
SqlQuery _getDataFingerprintQuery;
SqlQuery _setDataFingerprintQuery1;
SqlQuery _setDataFingerprintQuery2;
SqlQuery _setKeyValueStoreQuery;
SqlQuery _getKeyValueStoreQuery;
SqlQuery _deleteKeyValueStoreQuery;
SqlQuery _getConflictRecordQuery;
SqlQuery _setConflictRecordQuery;
SqlQuery _deleteConflictRecordQuery;
SqlQuery _getRawPinStateQuery;
SqlQuery _getEffectivePinStateQuery;
SqlQuery _getSubPinsQuery;
SqlQuery _countDehydratedFilesQuery;
SqlQuery _setPinStateQuery;
SqlQuery _wipePinStateQuery;
/* Storing etags to these folders, or their parent folders, is filtered out.
*
* When schedulePathForRemoteDiscovery() is called some etags to _invalid_ in the
@ -458,6 +419,8 @@ private:
* variable, for specific filesystems, or when WAL fails in a particular way.
*/
QByteArray _journalMode;
PreparedSqlQueryManager _queryManager;
};
bool OCSYNC_EXPORT

View File

@ -18,7 +18,9 @@
#include <libcrashreporter-gui/CrashReporter.h>
#include <QApplication>
#include <QDir>
#include <QDebug>
#include <QFileInfo>
int main(int argc, char *argv[])
{
@ -52,6 +54,14 @@ int main(int argc, char *argv[])
reporter.setWindowTitle(CRASHREPORTER_PRODUCT_NAME);
reporter.setText("<html><head/><body><p><span style=\" font-weight:600;\">Sorry!</span> " CRASHREPORTER_PRODUCT_NAME " crashed. Please tell us about it! " CRASHREPORTER_PRODUCT_NAME " has created an error report for you that can help improve the stability in the future. You can now send this report directly to the " CRASHREPORTER_PRODUCT_NAME " developers.</p></body></html>");
const QFileInfo crashLog(QDir::tempPath() + QStringLiteral("/" CRASHREPORTER_PRODUCT_NAME "-crash.log"));
if (crashLog.exists()) {
QFile inFile(crashLog.filePath());
if (inFile.open(QFile::ReadOnly)) {
reporter.setComment(inFile.readAll());
}
}
reporter.setReportData("BuildID", CRASHREPORTER_BUILD_ID);
reporter.setReportData("ProductName", CRASHREPORTER_PRODUCT_NAME);
reporter.setReportData("Version", CRASHREPORTER_VERSION_STRING);

View File

@ -199,13 +199,13 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QString &path, bool exclu
}
#endif
/* We create a Desktop.ini on Windows for the sidebar icon, make sure we don't sync it. */
if (blen == 11 && path == bname) {
if (bname.compare(QLatin1String("Desktop.ini"), Qt::CaseInsensitive) == 0) {
return CSYNC_FILE_SILENTLY_EXCLUDED;
}
/* Do not sync desktop.ini files anywhere in the tree. */
const auto desktopIniFile = QStringLiteral("desktop.ini");
if (blen == static_cast<size_t>(desktopIniFile.length()) && bname.compare(desktopIniFile, Qt::CaseInsensitive) == 0) {
return CSYNC_FILE_SILENTLY_EXCLUDED;
}
if (excludeConflictFiles && OCC::Utility::isConflictFile(path)) {
return CSYNC_FILE_EXCLUDE_CONFLICT;
}

View File

@ -34,6 +34,7 @@
#include "csync.h"
#include "vio/csync_vio_local.h"
#include "common/filesystembase.h"
#include "common/utility.h"
#include <QtCore/QLoggingCategory>
@ -52,8 +53,6 @@ struct csync_vio_handle_t {
QString path; // Always ends with '\'
};
static int _csync_vio_local_stat_mb(const QString &path, csync_file_stat_t *buf);
csync_vio_handle_t *csync_vio_local_opendir(const QString &name) {
QScopedPointer<csync_vio_handle_t> handle(new csync_vio_handle_t{});
@ -175,12 +174,9 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *h
file_stat->size = (handle->ffd.nFileSizeHigh * ((int64_t)(MAXDWORD)+1)) + handle->ffd.nFileSizeLow;
file_stat->modtime = FileTimeToUnixTime(&handle->ffd.ftLastWriteTime, &rem);
QString fullPath;
fullPath.reserve(handle->path.size() + std::wcslen(handle->ffd.cFileName));
fullPath += handle->path; // path always ends with '\', by construction
fullPath += QString::fromWCharArray(handle->ffd.cFileName);
// path always ends with '\', by construction
if (_csync_vio_local_stat_mb(fullPath, file_stat.get()) < 0) {
if (csync_vio_local_stat(handle->path + QString::fromWCharArray(handle->ffd.cFileName), file_stat.get()) < 0) {
// Will get excluded by _csync_detect_update.
file_stat->type = ItemTypeSkip;
}
@ -188,14 +184,7 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *h
return file_stat;
}
int csync_vio_local_stat(const QString &uri, csync_file_stat_t *buf)
{
int rc = _csync_vio_local_stat_mb(uri, buf);
return rc;
}
static int _csync_vio_local_stat_mb(const QString &path, csync_file_stat_t *buf)
{
/* Almost nothing to do since csync_vio_local_readdir already filled up most of the information
But we still need to fetch the file ID.
@ -206,21 +195,19 @@ static int _csync_vio_local_stat_mb(const QString &path, csync_file_stat_t *buf)
BY_HANDLE_FILE_INFORMATION fileInfo;
ULARGE_INTEGER FileIndex;
const auto longPath = OCC::FileSystem::longWinPath(path);
h = CreateFileW(longPath.toStdWString().data(), 0, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
nullptr );
h = CreateFileW(reinterpret_cast<const wchar_t *>(OCC::FileSystem::longWinPath(uri).utf16()), 0, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
if( h == INVALID_HANDLE_VALUE ) {
qCCritical(lcCSyncVIOLocal) << "CreateFileW failed on" << longPath;
errno = GetLastError();
qCCritical(lcCSyncVIOLocal) << "CreateFileW failed on" << uri << OCC::Utility::formatWinError(errno);
return -1;
}
if(!GetFileInformationByHandle( h, &fileInfo ) ) {
qCCritical(lcCSyncVIOLocal) << "GetFileInformationByHandle failed on" << longPath;
errno = GetLastError();
qCCritical(lcCSyncVIOLocal) << "GetFileInformationByHandle failed on" << uri << OCC::Utility::formatWinError(errno);
CloseHandle(h);
return -1;
}

View File

@ -87,7 +87,6 @@ set(client_SRCS
sharemanager.cpp
shareusergroupwidget.cpp
sharee.cpp
socketapi.cpp
sslbutton.cpp
sslerrordialog.cpp
syncrunfilelog.cpp
@ -338,6 +337,8 @@ target_link_libraries(nextcloudCore
Qt5::QuickControls2
)
add_subdirectory(socketapi)
if(Qt5WebEngine_FOUND AND Qt5WebEngineWidgets_FOUND)
target_link_libraries(nextcloudCore PUBLIC Qt5::WebEngineWidgets)
endif()

View File

@ -27,7 +27,7 @@
#include "folderman.h"
#include "logger.h"
#include "configfile.h"
#include "socketapi.h"
#include "socketapi/socketapi.h"
#include "sslerrordialog.h"
#include "theme.h"
#include "clientproxy.h"
@ -524,7 +524,12 @@ void Application::setupLogging()
logger->enterNextLogFile();
qCInfo(lcApplication) << QString::fromLatin1("################## %1 locale:[%2] ui_lang:[%3] version:[%4] os:[%5]").arg(_theme->appName()).arg(QLocale::system().name()).arg(property("ui_lang").toString()).arg(_theme->version()).arg(Utility::platformName());
qCInfo(lcApplication) << "##################" << _theme->appName()
<< "locale:" << QLocale::system().name()
<< "ui_lang:" << property("ui_lang")
<< "version:" << _theme->version()
<< "os:" << Utility::platformName();
qCInfo(lcApplication) << "Arguments:" << qApp->arguments();
}
void Application::slotUseMonoIconsChanged(bool)

View File

@ -28,7 +28,7 @@
#include "clientproxy.h"
#include "syncengine.h"
#include "syncrunfilelog.h"
#include "socketapi.h"
#include "socketapi/socketapi.h"
#include "theme.h"
#include "filesystem.h"
#include "localdiscoverytracker.h"
@ -353,7 +353,7 @@ void Folder::slotRunEtagJob()
// The _requestEtagJob is auto deleting itself on finish. Our guard pointer _requestEtagJob will then be null.
}
void Folder::etagRetrieved(const QString &etag, const QDateTime &tp)
void Folder::etagRetrieved(const QByteArray &etag, const QDateTime &tp)
{
// re-enable sync if it was disabled because network was down
FolderMan::instance()->setSyncEnabled(true);
@ -367,7 +367,7 @@ void Folder::etagRetrieved(const QString &etag, const QDateTime &tp)
_accountState->tagLastSuccessfullETagRequest(tp);
}
void Folder::etagRetrievedFromSyncEngine(const QString &etag, const QDateTime &time)
void Folder::etagRetrievedFromSyncEngine(const QByteArray &etag, const QDateTime &time)
{
qCInfo(lcFolder) << "Root etag from during sync:" << etag;
accountState()->tagLastSuccessfullETagRequest(time);
@ -874,42 +874,15 @@ void Folder::setSyncOptions()
opt._confirmExternalStorage = cfgFile.confirmExternalStorage();
opt._moveFilesToTrash = cfgFile.moveToTrash();
opt._vfs = _vfs;
opt._parallelNetworkJobs = _accountState->account()->isHttp2Supported() ? 20 : 6;
QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE");
if (!chunkSizeEnv.isEmpty()) {
opt._initialChunkSize = chunkSizeEnv.toUInt();
} else {
opt._initialChunkSize = cfgFile.chunkSize();
}
QByteArray minChunkSizeEnv = qgetenv("OWNCLOUD_MIN_CHUNK_SIZE");
if (!minChunkSizeEnv.isEmpty()) {
opt._minChunkSize = minChunkSizeEnv.toUInt();
} else {
opt._minChunkSize = cfgFile.minChunkSize();
}
QByteArray maxChunkSizeEnv = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE");
if (!maxChunkSizeEnv.isEmpty()) {
opt._maxChunkSize = maxChunkSizeEnv.toUInt();
} else {
opt._maxChunkSize = cfgFile.maxChunkSize();
}
opt._initialChunkSize = cfgFile.chunkSize();
opt._minChunkSize = cfgFile.minChunkSize();
opt._maxChunkSize = cfgFile.maxChunkSize();
opt._targetChunkUploadDuration = cfgFile.targetChunkUploadDuration();
int maxParallel = qgetenv("OWNCLOUD_MAX_PARALLEL").toUInt();
opt._parallelNetworkJobs = maxParallel ? maxParallel : _accountState->account()->isHttp2Supported() ? 20 : 6;
// Previously min/max chunk size values didn't exist, so users might
// have setups where the chunk size exceeds the new min/max default
// values. To cope with this, adjust min/max to always include the
// initial chunk size value.
opt._minChunkSize = qMin(opt._minChunkSize, opt._initialChunkSize);
opt._maxChunkSize = qMax(opt._maxChunkSize, opt._initialChunkSize);
QByteArray targetChunkUploadDurationEnv = qgetenv("OWNCLOUD_TARGET_CHUNK_UPLOAD_DURATION");
if (!targetChunkUploadDurationEnv.isEmpty()) {
opt._targetChunkUploadDuration = std::chrono::milliseconds(targetChunkUploadDurationEnv.toUInt());
} else {
opt._targetChunkUploadDuration = cfgFile.targetChunkUploadDuration();
}
opt.fillFromEnvironmentVariables();
opt.verifyChunkSizes();
_engine->setSyncOptions(opt);
}

View File

@ -376,8 +376,8 @@ private slots:
void slotItemCompleted(const SyncFileItemPtr &);
void slotRunEtagJob();
void etagRetrieved(const QString &, const QDateTime &tp);
void etagRetrievedFromSyncEngine(const QString &, const QDateTime &time);
void etagRetrieved(const QByteArray &, const QDateTime &tp);
void etagRetrievedFromSyncEngine(const QByteArray &, const QDateTime &time);
void slotEmitFinishedDelayed();
@ -447,7 +447,7 @@ private:
SyncResult _syncResult;
QScopedPointer<SyncEngine> _engine;
QPointer<RequestEtagJob> _requestEtagJob;
QString _lastEtag;
QByteArray _lastEtag;
QElapsedTimer _timeSinceLastSyncDone;
QElapsedTimer _timeSinceLastSyncStart;
QElapsedTimer _timeSinceLastFullLocalDiscovery;

View File

@ -17,7 +17,7 @@
#include "folder.h"
#include "syncresult.h"
#include "theme.h"
#include "socketapi.h"
#include "socketapi/socketapi.h"
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"

View File

@ -201,21 +201,19 @@ void FolderWizardRemotePath::slotCreateRemoteFolder(const QString &folder)
auto *job = new MkColJob(_account, fullPath, this);
/* check the owncloud configuration file and query the ownCloud */
connect(job, static_cast<void (MkColJob::*)(QNetworkReply::NetworkError)>(&MkColJob::finished),
connect(job, &MkColJob::finishedWithoutError,
this, &FolderWizardRemotePath::slotCreateRemoteFolderFinished);
connect(job, &AbstractNetworkJob::networkError, this, &FolderWizardRemotePath::slotHandleMkdirNetworkError);
job->start();
}
void FolderWizardRemotePath::slotCreateRemoteFolderFinished(QNetworkReply::NetworkError error)
void FolderWizardRemotePath::slotCreateRemoteFolderFinished()
{
if (error == QNetworkReply::NoError) {
qCDebug(lcWizard) << "webdav mkdir request finished";
showWarn(tr("Folder was successfully created on %1.").arg(Theme::instance()->appNameGUI()));
slotRefreshFolders();
_ui.folderEntry->setText(static_cast<MkColJob *>(sender())->path());
slotLsColFolderEntry();
}
qCDebug(lcWizard) << "webdav mkdir request finished";
showWarn(tr("Folder was successfully created on %1.").arg(Theme::instance()->appNameGUI()));
slotRefreshFolders();
_ui.folderEntry->setText(static_cast<MkColJob *>(sender())->path());
slotLsColFolderEntry();
}
void FolderWizardRemotePath::slotHandleMkdirNetworkError(QNetworkReply *reply)

View File

@ -92,7 +92,7 @@ protected slots:
void showWarn(const QString & = QString()) const;
void slotAddRemoteFolder();
void slotCreateRemoteFolder(const QString &);
void slotCreateRemoteFolderFinished(QNetworkReply::NetworkError error);
void slotCreateRemoteFolderFinished();
void slotHandleMkdirNetworkError(QNetworkReply *);
void slotHandleLsColNetworkError(QNetworkReply *);
void slotUpdateDirectories(const QStringList &);

View File

@ -536,26 +536,28 @@ void OwncloudSetupWizard::createRemoteFolder()
_ocWizard->appendToConfigurationLog(tr("creating folder on Nextcloud: %1").arg(_remoteFolder));
auto *job = new MkColJob(_ocWizard->account(), _remoteFolder, this);
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)), SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
connect(job, &MkColJob::finishedWithError, this, &OwncloudSetupWizard::slotCreateRemoteFolderFinished);
connect(job, &MkColJob::finishedWithoutError, this, [this] {
_ocWizard->appendToConfigurationLog(tr("Remote folder %1 created successfully.").arg(_remoteFolder));
finalizeSetup(true);
});
job->start();
}
void OwncloudSetupWizard::slotCreateRemoteFolderFinished(QNetworkReply::NetworkError error)
void OwncloudSetupWizard::slotCreateRemoteFolderFinished(QNetworkReply *reply)
{
auto error = reply->error();
qCDebug(lcWizard) << "** webdav mkdir request finished " << error;
// disconnect(ownCloudInfo::instance(), SIGNAL(webdavColCreated(QNetworkReply::NetworkError)),
// this, SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
bool success = true;
if (error == QNetworkReply::NoError) {
_ocWizard->appendToConfigurationLog(tr("Remote folder %1 created successfully.").arg(_remoteFolder));
} else if (error == 202) {
if (error == 202) {
_ocWizard->appendToConfigurationLog(tr("The remote folder %1 already exists. Connecting it for syncing.").arg(_remoteFolder));
} else if (error > 202 && error < 300) {
_ocWizard->displayError(tr("The folder creation resulted in HTTP error code %1").arg((int)error), false);
_ocWizard->displayError(tr("The folder creation resulted in HTTP error code %1").arg(static_cast<int>(error)), false);
_ocWizard->appendToConfigurationLog(tr("The folder creation resulted in HTTP error code %1").arg((int)error));
_ocWizard->appendToConfigurationLog(tr("The folder creation resulted in HTTP error code %1").arg(static_cast<int>(error)));
} else if (error == QNetworkReply::OperationCanceledError) {
_ocWizard->displayError(tr("The remote folder creation failed because the provided credentials "
"are wrong!"

View File

@ -65,7 +65,7 @@ private slots:
void slotCreateLocalAndRemoteFolders(const QString &, const QString &);
void slotRemoteFolderExists(QNetworkReply *);
void slotCreateRemoteFolderFinished(QNetworkReply::NetworkError);
void slotCreateRemoteFolderFinished(QNetworkReply *reply);
void slotAssistantFinished(int);
void slotSkipFolderConfiguration();

View File

@ -0,0 +1,8 @@
target_sources(nextcloudCore PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/socketapi.cpp
${CMAKE_CURRENT_SOURCE_DIR}/socketuploadjob.cpp
)
if( APPLE )
target_sources(nextcloudCore PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/socketapisocket_mac.mm)
endif()

View File

@ -16,9 +16,11 @@
#include "socketapi.h"
#include "socketapi_p.h"
#include "socketapi/socketuploadjob.h"
#include "conflictdialog.h"
#include "conflictsolver.h"
#include "config.h"
#include "configfile.h"
#include "folderman.h"
@ -32,6 +34,7 @@
#include "account.h"
#include "accountstate.h"
#include "account.h"
#include "accountmanager.h"
#include "capabilities.h"
#include "common/asserts.h"
#include "guiutility.h"
@ -57,6 +60,7 @@
#include <QAction>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QWidget>
@ -78,12 +82,25 @@
#define MIRALL_SOCKET_API_VERSION "1.1"
namespace {
const QLatin1Char RecordSeparator()
{
return QLatin1Char('\x1e');
}
QStringList split(const QString &data)
{
// TODO: string ref?
return data.split(RecordSeparator());
}
#if GUI_TESTING
using namespace OCC;
QList<QObject*> allObjects(const QList<QWidget*> &widgets) {
QList<QObject*> objects;
QList<QObject *> allObjects(const QList<QWidget *> &widgets)
{
QList<QObject *> objects;
std::copy(widgets.constBegin(), widgets.constEnd(), std::back_inserter(objects));
objects << qApp;
@ -91,11 +108,11 @@ QList<QObject*> allObjects(const QList<QWidget*> &widgets) {
return objects;
}
QObject *findWidget(const QString &queryString, const QList<QWidget*> &widgets = QApplication::allWidgets())
QObject *findWidget(const QString &queryString, const QList<QWidget *> &widgets = QApplication::allWidgets())
{
auto objects = allObjects(widgets);
QList<QObject*>::const_iterator foundWidget;
QList<QObject *>::const_iterator foundWidget;
if (queryString.contains('>')) {
qCDebug(lcSocketApi) << "queryString contains >";
@ -107,33 +124,34 @@ QObject *findWidget(const QString &queryString, const QList<QWidget*> &widgets =
qCDebug(lcSocketApi) << "Find parent: " << parentQueryString;
auto parent = findWidget(parentQueryString);
if(!parent) {
if (!parent) {
return nullptr;
}
auto childQueryString = subQueries[1].trimmed();
auto child = findWidget(childQueryString, parent->findChildren<QWidget*>());
auto child = findWidget(childQueryString, parent->findChildren<QWidget *>());
qCDebug(lcSocketApi) << "found child: " << !!child;
return child;
} else if(queryString.startsWith('#')) {
} else if (queryString.startsWith('#')) {
auto objectName = queryString.mid(1);
qCDebug(lcSocketApi) << "find objectName: " << objectName;
foundWidget = std::find_if(objects.constBegin(), objects.constEnd(), [&](QObject *widget) {
return widget->objectName() == objectName;
});
} else {
QList<QObject*> matches;
std::copy_if(objects.constBegin(), objects.constEnd(), std::back_inserter(matches), [&](QObject* widget) {
QList<QObject *> matches;
std::copy_if(objects.constBegin(), objects.constEnd(), std::back_inserter(matches), [&](QObject *widget) {
return widget->inherits(queryString.toLatin1());
});
std::for_each(matches.constBegin(), matches.constEnd(), [](QObject* w) {
if(!w) return;
std::for_each(matches.constBegin(), matches.constEnd(), [](QObject *w) {
if (!w)
return;
qCDebug(lcSocketApi) << "WIDGET: " << w->objectName() << w->metaObject()->className();
});
if(matches.empty()) {
if (matches.empty()) {
return nullptr;
}
return matches[0];
@ -200,16 +218,6 @@ void SocketListener::sendMessage(const QString &message, bool doWait) const
}
}
struct ListenerHasSocketPred
{
QIODevice *socket;
ListenerHasSocketPred(QIODevice *socket)
: socket(socket)
{
}
bool operator()(const SocketListener &listener) const { return listener.socket == socket; }
};
SocketApi::SocketApi(QObject *parent)
: QObject(parent)
{
@ -217,6 +225,7 @@ SocketApi::SocketApi(QObject *parent)
qRegisterMetaType<SocketListener *>("SocketListener*");
qRegisterMetaType<QSharedPointer<SocketApiJob>>("QSharedPointer<SocketApiJob>");
qRegisterMetaType<QSharedPointer<SocketApiJobV2>>("QSharedPointer<SocketApiJobV2>");
if (Utility::isWindows()) {
socketPath = QLatin1String(R"(\\.\pipe\)")
@ -236,21 +245,20 @@ SocketApi::SocketApi(QObject *parent)
CFURLRef url = (CFURLRef)CFAutorelease((CFURLRef)CFBundleCopyBundleURL(CFBundleGetMainBundle()));
QString bundlePath = QUrl::fromCFURL(url).path();
auto _system = [](const QString &cmd, const QStringList &args){
auto _system = [](const QString &cmd, const QStringList &args) {
QProcess process;
process.setProcessChannelMode(QProcess::MergedChannels);
process.start(cmd, args);
if (!process.waitForFinished())
{
if (!process.waitForFinished()) {
qCWarning(lcSocketApi) << "Failed to load shell extension:" << cmd << args.join(" ") << process.errorString();
} else {
qCInfo(lcSocketApi) << (process.exitCode() != 0 ? "Failed to load" : "Loaded") << "shell extension:" << cmd << args.join(" ") << process.readAll();
qCInfo(lcSocketApi) << (process.exitCode() != 0 ? "Failed to load" : "Loaded") << "shell extension:" << cmd << args.join(" ") << process.readAll();
}
};
// Add it again. This was needed for Mojave to trigger a load.
_system(QStringLiteral("pluginkit"), {QStringLiteral("-a"),QStringLiteral("%1Contents/PlugIns/FinderSyncExt.appex/").arg(bundlePath)});
_system(QStringLiteral("pluginkit"), { QStringLiteral("-a"), QStringLiteral("%1Contents/PlugIns/FinderSyncExt.appex/").arg(bundlePath) });
// Tell Finder to use the Extension (checking it from System Preferences -> Extensions)
_system(QStringLiteral("pluginkit"), {QStringLiteral("-e"), QStringLiteral("use"), QStringLiteral("-i"), QStringLiteral(APPLICATION_REV_DOMAIN ".FinderSyncExt")});
_system(QStringLiteral("pluginkit"), { QStringLiteral("-e"), QStringLiteral("use"), QStringLiteral("-i"), QStringLiteral(APPLICATION_REV_DOMAIN ".FinderSyncExt") });
#endif
} else if (Utility::isLinux() || Utility::isBSD()) {
@ -288,7 +296,7 @@ SocketApi::~SocketApi()
qCDebug(lcSocketApi) << "dtor";
_localServer.close();
// All remaining sockets will be destroyed with _localServer, their parent
ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer);
ASSERT(_listeners.isEmpty() || _listeners.first()->socket->parent() == &_localServer)
_listeners.clear();
}
@ -307,14 +315,13 @@ void SocketApi::slotNewConnection()
connect(socket, &QObject::destroyed, this, &SocketApi::slotSocketDestroyed);
ASSERT(socket->readAll().isEmpty());
_listeners.append(SocketListener(socket));
SocketListener &listener = _listeners.last();
foreach (Folder *f, FolderMan::instance()->map()) {
auto listener = QSharedPointer<SocketListener>::create(socket);
_listeners.insert(socket, listener);
for (Folder *f : FolderMan::instance()->map()) {
if (f->canSync()) {
QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener.socket;
listener.sendMessage(message);
qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener->socket;
listener->sendMessage(message);
}
}
}
@ -326,13 +333,13 @@ void SocketApi::onLostConnection()
auto socket = qobject_cast<QIODevice *>(sender());
ASSERT(socket);
_listeners.erase(std::remove_if(_listeners.begin(), _listeners.end(), ListenerHasSocketPred(socket)), _listeners.end());
_listeners.remove(socket);
}
void SocketApi::slotSocketDestroyed(QObject *obj)
{
auto *socket = static_cast<QIODevice *>(obj);
_listeners.erase(std::remove_if(_listeners.begin(), _listeners.end(), ListenerHasSocketPred(socket)), _listeners.end());
_listeners.remove(socket);
}
void SocketApi::slotReadSocket()
@ -346,36 +353,38 @@ void SocketApi::slotReadSocket()
// the readyRead() signals are received - in that case there won't be a
// valid listener. We execute the handler anyway, but it will work with
// a SocketListener that doesn't send any messages.
static auto noListener = SocketListener(nullptr);
SocketListener *listener = &noListener;
auto listenerIt = std::find_if(_listeners.begin(), _listeners.end(), ListenerHasSocketPred(socket));
if (listenerIt != _listeners.end()) {
listener = &*listenerIt;
}
static auto invalidListener = QSharedPointer<SocketListener>::create(nullptr);
const auto listener = _listeners.value(socket, invalidListener);
while (socket->canReadLine()) {
// Make sure to normalize the input from the socket to
// make sure that the path will match, especially on OS X.
QString line = QString::fromUtf8(socket->readLine()).normalized(QString::NormalizationForm_C);
line.chop(1); // remove the '\n'
const QString line = QString::fromUtf8(socket->readLine().trimmed()).normalized(QString::NormalizationForm_C);
qCInfo(lcSocketApi) << "Received SocketAPI message <--" << line << "from" << socket;
QByteArray command = line.split(":").value(0).toLatin1();
const int argPos = line.indexOf(QLatin1Char(':'));
const QByteArray command = line.midRef(0, argPos).toUtf8().toUpper();
const int indexOfMethod = [&] {
QByteArray functionWithArguments = QByteArrayLiteral("command_");
if (command.startsWith("ASYNC_")) {
functionWithArguments += command + QByteArrayLiteral("(QSharedPointer<SocketApiJob>)");
} else if (command.startsWith("V2/")) {
functionWithArguments += QByteArrayLiteral("V2_") + command.mid(3) + QByteArrayLiteral("(QSharedPointer<SocketApiJobV2>)");
} else {
functionWithArguments += command + QByteArrayLiteral("(QString,SocketListener*)");
}
Q_ASSERT(staticQtMetaObject.normalizedSignature(functionWithArguments) == functionWithArguments);
const auto out = staticMetaObject.indexOfMethod(functionWithArguments);
if (out == -1) {
listener->sendError(QStringLiteral("Function %1 not found").arg(QString::fromUtf8(functionWithArguments)));
}
ASSERT(out != -1)
return out;
}();
QByteArray functionWithArguments = "command_" + command;
const auto argument = argPos != -1 ? line.midRef(argPos + 1) : QStringRef();
if (command.startsWith("ASYNC_")) {
functionWithArguments += "(QSharedPointer<SocketApiJob>)";
} else {
functionWithArguments += "(QString,SocketListener*)";
}
int indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments);
QString argument = line.remove(0, command.length() + 1);
if (command.startsWith("ASYNC_")) {
auto arguments = argument.split('|');
if (arguments.size() != 2) {
listener->sendMessage(QLatin1String("argument count is wrong"));
listener->sendError(QStringLiteral("argument count is wrong"));
return;
}
@ -384,25 +393,41 @@ void SocketApi::slotReadSocket()
auto jobId = arguments[0];
auto socketApiJob = QSharedPointer<SocketApiJob>(
new SocketApiJob(jobId, listener, json), &QObject::deleteLater);
new SocketApiJob(jobId.toString(), listener, json), &QObject::deleteLater);
if (indexOfMethod != -1) {
staticMetaObject.method(indexOfMethod)
.invoke(this, Qt::QueuedConnection,
Q_ARG(QSharedPointer<SocketApiJob>, socketApiJob));
Q_ARG(QSharedPointer<SocketApiJob>, socketApiJob));
} else {
qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command
<< "with argument:" << argument;
socketApiJob->reject("command not found");
<< "with argument:" << argument;
socketApiJob->reject(QStringLiteral("command not found"));
}
} else if (command.startsWith("V2/")) {
QJsonParseError error;
const auto json = QJsonDocument::fromJson(argument.toUtf8(), &error).object();
if (error.error != QJsonParseError::NoError) {
qCWarning(lcSocketApi()) << "Invalid json" << argument.toString() << error.errorString();
listener->sendError(error.errorString());
return;
}
auto socketApiJob = QSharedPointer<SocketApiJobV2>::create(listener, command, json);
if (indexOfMethod != -1) {
staticMetaObject.method(indexOfMethod)
.invoke(this, Qt::QueuedConnection,
Q_ARG(QSharedPointer<SocketApiJobV2>, socketApiJob));
} else {
qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command
<< "with argument:" << argument;
socketApiJob->failure(QStringLiteral("command not found"));
}
} else {
if (indexOfMethod != -1) {
// to ensure that listener is still valid we need to call it with Qt::DirectConnection
ASSERT(thread() == QThread::currentThread())
staticMetaObject.method(indexOfMethod)
.invoke(this, Qt::DirectConnection, Q_ARG(QString, argument),
Q_ARG(SocketListener *, listener));
} else {
qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command << "with argument:" << argument;
.invoke(this, Qt::DirectConnection, Q_ARG(QString, argument.toString()),
Q_ARG(SocketListener *, listener.data()));
}
}
}
@ -416,10 +441,10 @@ void SocketApi::slotRegisterPath(const QString &alias)
Folder *f = FolderMan::instance()->folder(alias);
if (f) {
QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
foreach (auto &listener, _listeners) {
qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener.socket;
listener.sendMessage(message);
const QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
for (const auto &listener : qAsConst(_listeners)) {
qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener->socket;
listener->sendMessage(message);
}
}
@ -464,8 +489,8 @@ void SocketApi::slotUpdateFolderView(Folder *f)
void SocketApi::broadcastMessage(const QString &msg, bool doWait)
{
foreach (auto &listener, _listeners) {
listener.sendMessage(msg, doWait);
for (const auto &listener : qAsConst(_listeners)) {
listener->sendMessage(msg, doWait);
}
}
@ -515,8 +540,8 @@ void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileSt
QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString());
Q_ASSERT(!systemPath.endsWith('/'));
uint directoryHash = qHash(systemPath.left(systemPath.lastIndexOf('/')));
foreach (auto &listener, _listeners) {
listener.sendMessageIfDirectoryMonitored(msg, directoryHash);
for (const auto &listener : qAsConst(_listeners)) {
listener->sendMessageIfDirectoryMonitored(msg, directoryHash);
}
}
@ -612,20 +637,20 @@ class GetOrCreatePublicLinkShare : public QObject
Q_OBJECT
public:
GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile,
std::function<void(const QString &link)> targetFun, QObject *parent)
QObject *parent)
: QObject(parent)
, _account(account)
, _shareManager(account)
, _localFile(localFile)
, _targetFun(targetFun)
{
connect(&_shareManager, &ShareManager::sharesFetched,
this, &GetOrCreatePublicLinkShare::sharesFetched);
connect(&_shareManager, &ShareManager::linkShareCreated,
this, &GetOrCreatePublicLinkShare::linkShareCreated);
connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
this, &GetOrCreatePublicLinkShare::linkShareRequiresPassword);
connect(&_shareManager, &ShareManager::serverError,
this, &GetOrCreatePublicLinkShare::serverError);
connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
this, &GetOrCreatePublicLinkShare::passwordRequired);
}
void run()
@ -638,6 +663,7 @@ private slots:
void sharesFetched(const QList<QSharedPointer<Share>> &shares)
{
auto shareName = SocketApi::tr("Context menu share");
// If there already is a context menu share, reuse it
for (const auto &share : shares) {
const auto linkShare = qSharedPointerDynamicCast<LinkShare>(share);
@ -679,6 +705,13 @@ private slots:
_shareManager.createLinkShare(_localFile, QString(), password);
}
void linkShareRequiresPassword(const QString &message)
{
qCInfo(lcPublicLink) << "Could not create link share:" << message;
emit error(message);
deleteLater();
}
void serverError(int code, const QString &message)
{
qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
@ -688,19 +721,24 @@ private slots:
tr("Could not retrieve or create the public link share. Error:\n\n%1").arg(message),
QMessageBox::Ok,
QMessageBox::NoButton);
emit error(message);
deleteLater();
}
signals:
void done(const QString &link);
void error(const QString &message);
private:
void success(const QString &link)
{
_targetFun(link);
emit done(link);
deleteLater();
}
AccountPtr _account;
ShareManager _shareManager;
QString _localFile;
std::function<void(const QString &url)> _targetFun;
};
#else
@ -728,7 +766,11 @@ void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListene
return;
AccountPtr account = fileData.folder->accountState()->account();
auto job = new GetOrCreatePublicLinkShare(account, fileData.serverRelativePath, [](const QString &url) { copyUrlToClipboard(url); }, this);
auto job = new GetOrCreatePublicLinkShare(account, fileData.serverRelativePath, this);
connect(job, &GetOrCreatePublicLinkShare::done, this,
[](const QString &url) { copyUrlToClipboard(url); });
connect(job, &GetOrCreatePublicLinkShare::error, this,
[=]() { emit shareCommandReceived(fileData.serverRelativePath, fileData.localPath, ShareDialogStartPage::PublicLinks); });
job->run();
}
@ -788,7 +830,7 @@ void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListen
void SocketApi::command_MAKE_AVAILABLE_LOCALLY(const QString &filesArg, SocketListener *)
{
QStringList files = filesArg.split(QLatin1Char('\x1e')); // Record Separator
const QStringList files = split(filesArg);
for (const auto &file : files) {
auto data = FileData::get(file);
@ -809,7 +851,7 @@ void SocketApi::command_MAKE_AVAILABLE_LOCALLY(const QString &filesArg, SocketLi
/* Go over all the files and replace them by a virtual file */
void SocketApi::command_MAKE_ONLINE_ONLY(const QString &filesArg, SocketListener *)
{
QStringList files = filesArg.split(QLatin1Char('\x1e')); // Record Separator
const QStringList files = split(filesArg);
for (const auto &file : files) {
auto data = FileData::get(file);
@ -903,6 +945,22 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *)
solver.setRemoteVersionFilename(target);
}
void SocketApi::command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const
{
QJsonArray out;
for (auto acc : AccountManager::instance()->accounts()) {
// TODO: Use uuid once https://github.com/owncloud/client/pull/8397 is merged
out << QJsonObject({ { "name", acc->account()->displayName() }, { "id", acc->account()->id() } });
}
job->success({ { "accounts", out } });
}
void SocketApi::command_V2_UPLOAD_FILES_FROM(const QSharedPointer<SocketApiJobV2> &job) const
{
auto uploadJob = new SocketUploadJob(job);
uploadJob->start();
}
void SocketApi::emailPrivateLink(const QString &link)
{
Utility::openEmailComposer(
@ -948,8 +1006,7 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi
// If sharing is globally disabled, do not show any sharing entries.
// If there is no permission to share for this file, add a disabled entry saying so
if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + (!record.isDirectory()
? tr("Resharing this file is not allowed") : tr("Resharing this folder is not allowed")));
listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + (!record.isDirectory() ? tr("Resharing this file is not allowed") : tr("Resharing this folder is not allowed")));
} else {
listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share options"));
@ -1031,7 +1088,7 @@ SocketApi::FileData SocketApi::FileData::parentFolder() const
void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener)
{
listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN"));
QStringList files = argument.split(QLatin1Char('\x1e')); // Record Separator
const QStringList files = split(argument);
// Find the common sync folder.
// syncFolder will be null if files are in different folders.
@ -1149,13 +1206,13 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
// TODO: Should be a submenu, should use icons
auto makePinContextMenu = [&](bool makeAvailableLocally, bool freeSpace) {
listener->sendMessage(QLatin1String("MENU_ITEM:CURRENT_PIN:d:")
+ Utility::vfsCurrentAvailabilityText(*combined));
+ Utility::vfsCurrentAvailabilityText(*combined));
listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_AVAILABLE_LOCALLY:")
+ (makeAvailableLocally ? QLatin1String(":") : QLatin1String("d:"))
+ Utility::vfsPinActionText());
+ (makeAvailableLocally ? QLatin1String(":") : QLatin1String("d:"))
+ Utility::vfsPinActionText());
listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_ONLINE_ONLY:")
+ (freeSpace ? QLatin1String(":") : QLatin1String("d:"))
+ Utility::vfsFreeSpaceActionText());
+ (freeSpace ? QLatin1String(":") : QLatin1String("d:"))
+ Utility::vfsFreeSpaceActionText());
};
if (combined) {
@ -1241,20 +1298,20 @@ void SocketApi::command_ASYNC_GET_WIDGET_PROPERTY(const QSharedPointer<SocketApi
auto segments = propertyName.split('.');
QObject* currentObject = widget;
QObject *currentObject = widget;
QString value;
for(int i = 0;i<segments.count(); i++) {
for (int i = 0; i < segments.count(); i++) {
auto segment = segments.at(i);
auto var = currentObject->property(segment.toUtf8().constData());
if(var.canConvert<QString>()) {
if (var.canConvert<QString>()) {
var.convert(QMetaType::QString);
value = var.value<QString>();
break;
}
auto tmpObject = var.value<QObject*>();
if(tmpObject) {
auto tmpObject = var.value<QObject *>();
if (tmpObject) {
currentObject = tmpObject;
} else {
QString message = QString(QLatin1String("Widget not found: 3: %1")).arg(widgetName);
@ -1277,7 +1334,7 @@ void SocketApi::command_ASYNC_SET_WIDGET_PROPERTY(const QSharedPointer<SocketApi
return;
}
widget->setProperty(arguments["property"].toString().toUtf8().constData(),
arguments["value"]);
arguments["value"]);
job->resolve();
}
@ -1345,20 +1402,20 @@ void SocketApi::command_ASYNC_ASSERT_ICON_IS_EQUAL(const QSharedPointer<SocketAp
auto segments = propertyName.split('.');
QObject* currentObject = widget;
QObject *currentObject = widget;
QIcon value;
for(int i = 0;i<segments.count(); i++) {
for (int i = 0; i < segments.count(); i++) {
auto segment = segments.at(i);
auto var = currentObject->property(segment.toUtf8().constData());
if(var.canConvert<QIcon>()) {
if (var.canConvert<QIcon>()) {
var.convert(QMetaType::QIcon);
value = var.value<QIcon>();
break;
}
auto tmpObject = var.value<QObject*>();
if(tmpObject) {
auto tmpObject = var.value<QObject *>();
if (tmpObject) {
currentObject = tmpObject;
} else {
job->reject(QString(QLatin1String("Icon not found: %1")).arg(propertyName));
@ -1366,12 +1423,11 @@ void SocketApi::command_ASYNC_ASSERT_ICON_IS_EQUAL(const QSharedPointer<SocketAp
}
auto iconName = job->arguments()[QLatin1String("iconName")].toString();
if (value.name() == iconName) {
if (value.name() == iconName) {
job->resolve();
} else {
job->reject("iconName " + iconName + " does not match: " + value.name());
}
}
#endif
@ -1383,6 +1439,46 @@ QString SocketApi::buildRegisterPathMessage(const QString &path)
return message;
}
void SocketApiJob::resolve(const QString &response)
{
_socketListener->sendMessage(QStringLiteral("RESOLVE|") + _jobId + QLatin1Char('|') + response);
}
void SocketApiJob::resolve(const QJsonObject &response)
{
resolve(QJsonDocument { response }.toJson());
}
void SocketApiJob::reject(const QString &response)
{
_socketListener->sendMessage(QStringLiteral("REJECT|") + _jobId + QLatin1Char('|') + response);
}
SocketApiJobV2::SocketApiJobV2(const QSharedPointer<SocketListener> &socketListener, const QByteArray &command, const QJsonObject &arguments)
: _socketListener(socketListener)
, _command(command)
, _jobId(arguments[QStringLiteral("id")].toString())
, _arguments(arguments[QStringLiteral("arguments")].toObject())
{
ASSERT(!_jobId.isEmpty())
}
void SocketApiJobV2::success(const QJsonObject &response) const
{
doFinish(response);
}
void SocketApiJobV2::failure(const QString &error) const
{
doFinish({ { QStringLiteral("error"), error } });
}
void SocketApiJobV2::doFinish(const QJsonObject &obj) const
{
_socketListener->sendMessage(_command + QStringLiteral("_RESULT:") + QJsonDocument({ { QStringLiteral("id"), _jobId }, { QStringLiteral("arguments"), obj } }).toJson(QJsonDocument::Compact));
Q_EMIT finished();
}
} // namespace OCC
#include "socketapi.moc"

View File

@ -40,6 +40,7 @@ class Folder;
class SocketListener;
class DirectEditor;
class SocketApiJob;
class SocketApiJobV2;
Q_DECLARE_LOGGING_CATEGORY(lcSocketApi)
@ -129,6 +130,10 @@ private:
Q_INVOKABLE void command_OPEN(const QString &localFile, SocketListener *listener);
#endif
// External sync
Q_INVOKABLE void command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const;
Q_INVOKABLE void command_V2_UPLOAD_FILES_FROM(const QSharedPointer<SocketApiJobV2> &job) const;
// Fetch the private link and call targetFun
void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun);
@ -164,7 +169,7 @@ private:
QString buildRegisterPathMessage(const QString &path);
QSet<QString> _registeredAliases;
QList<SocketListener> _listeners;
QMap<QIODevice *, QSharedPointer<SocketListener>> _listeners;
SocketApiServer _localServer;
};
}

View File

@ -62,12 +62,20 @@ class SocketListener
public:
QPointer<QIODevice> socket;
explicit SocketListener(QIODevice *socket)
: socket(socket)
explicit SocketListener(QIODevice *_socket)
: socket(_socket)
{
}
void sendMessage(const QString &message, bool doWait = false) const;
void sendWarning(const QString &message, bool doWait = false) const
{
sendMessage(QStringLiteral("WARNING:") + message, doWait);
}
void sendError(const QString &message, bool doWait = false) const
{
sendMessage(QStringLiteral("ERROR:") + message, doWait);
}
void sendMessageIfDirectoryMonitored(const QString &message, uint systemDirectoryHash) const
{
@ -109,30 +117,48 @@ class SocketApiJob : public QObject
{
Q_OBJECT
public:
SocketApiJob(const QString &jobId, SocketListener *socketListener, const QJsonObject &arguments)
explicit SocketApiJob(const QString &jobId, const QSharedPointer<SocketListener> &socketListener, const QJsonObject &arguments)
: _jobId(jobId)
, _socketListener(socketListener)
, _arguments(arguments)
{
}
void resolve(const QString &response = QString())
{
_socketListener->sendMessage(QLatin1String("RESOLVE|") + _jobId + '|' + response);
}
void resolve(const QString &response = QString());
void resolve(const QJsonObject &response) { resolve(QJsonDocument{ response }.toJson()); }
void resolve(const QJsonObject &response);
const QJsonObject &arguments() { return _arguments; }
void reject(const QString &response)
{
_socketListener->sendMessage(QLatin1String("REJECT|") + _jobId + '|' + response);
}
void reject(const QString &response);
protected:
QString _jobId;
QSharedPointer<SocketListener> _socketListener;
QJsonObject _arguments;
};
class SocketApiJobV2 : public QObject
{
Q_OBJECT
public:
explicit SocketApiJobV2(const QSharedPointer<SocketListener> &socketListener, const QByteArray &command, const QJsonObject &arguments);
void success(const QJsonObject &response) const;
void failure(const QString &error) const;
const QJsonObject &arguments() const { return _arguments; }
QByteArray command() const { return _command; }
Q_SIGNALS:
void finished() const;
private:
void doFinish(const QJsonObject &obj) const;
QSharedPointer<SocketListener> _socketListener;
const QByteArray _command;
QString _jobId;
SocketListener *_socketListener;
QJsonObject _arguments;
};
}

View File

@ -16,7 +16,7 @@
#import <Cocoa/Cocoa.h>
@protocol ChannelProtocol <NSObject>
- (void)sendMessage:(NSData*)msg;
- (void)sendMessage:(NSData *)msg;
@end
@protocol RemoteEndProtocol <NSObject, ChannelProtocol>
@ -31,7 +31,7 @@
@interface Server : NSObject
@property SocketApiServerPrivate *wrapper;
- (instancetype)initWithWrapper:(SocketApiServerPrivate *)wrapper;
- (void)registerClient:(NSDistantObject <RemoteEndProtocol> *)remoteEnd;
- (void)registerClient:(NSDistantObject<RemoteEndProtocol> *)remoteEnd;
@end
class SocketApiSocketPrivate
@ -39,13 +39,13 @@ class SocketApiSocketPrivate
public:
SocketApiSocket *q_ptr;
SocketApiSocketPrivate(NSDistantObject <ChannelProtocol> *remoteEnd);
SocketApiSocketPrivate(NSDistantObject<ChannelProtocol> *remoteEnd);
~SocketApiSocketPrivate();
// release remoteEnd
void disconnectRemote();
NSDistantObject <ChannelProtocol> *remoteEnd;
NSDistantObject<ChannelProtocol> *remoteEnd;
LocalEnd *localEnd;
QByteArray inBuffer;
bool isRemoteDisconnected = false;
@ -59,7 +59,7 @@ public:
SocketApiServerPrivate();
~SocketApiServerPrivate();
QList<SocketApiSocket*> pendingConnections;
QList<SocketApiSocket *> pendingConnections;
NSConnection *connection;
Server *server;
};
@ -73,7 +73,7 @@ public:
return self;
}
- (void)sendMessage:(NSData*)msg
- (void)sendMessage:(NSData *)msg
{
if (_wrapper) {
_wrapper->inBuffer += QByteArray::fromRawNSData(msg);
@ -81,7 +81,7 @@ public:
}
}
- (void)connectionDidDie:(NSNotification*)notification
- (void)connectionDidDie:(NSNotification *)notification
{
#pragma unused(notification)
if (_wrapper) {
@ -99,7 +99,7 @@ public:
return self;
}
- (void)registerClient:(NSDistantObject <RemoteEndProtocol> *)remoteEnd
- (void)registerClient:(NSDistantObject<RemoteEndProtocol> *)remoteEnd
{
// This saves a few mach messages that would otherwise be needed to query the interface
[remoteEnd setProtocolForProxy:@protocol(RemoteEndProtocol)];
@ -150,7 +150,7 @@ qint64 SocketApiSocket::writeData(const char *data, qint64 len)
// Since FinderSync already runs in a separate process, blocking isn't too critical.
[d->remoteEnd sendMessage:[NSData dataWithBytesNoCopy:const_cast<char *>(data) length:len freeWhenDone:NO]];
return len;
} @catch(NSException* e) {
} @catch (NSException *e) {
// connectionDidDie can be notified too late, also interpret any sending exception as a disconnection.
d->disconnectRemote();
emit disconnected();
@ -170,16 +170,16 @@ bool SocketApiSocket::canReadLine() const
return d->inBuffer.indexOf('\n', int(pos())) != -1 || QIODevice::canReadLine();
}
SocketApiSocketPrivate::SocketApiSocketPrivate(NSDistantObject <ChannelProtocol> *remoteEnd)
SocketApiSocketPrivate::SocketApiSocketPrivate(NSDistantObject<ChannelProtocol> *remoteEnd)
: remoteEnd(remoteEnd)
, localEnd([[LocalEnd alloc] initWithWrapper:this])
{
[remoteEnd retain];
// (Ab)use our objective-c object just to catch the notification
[[NSNotificationCenter defaultCenter] addObserver:localEnd
selector:@selector(connectionDidDie:)
name:NSConnectionDidDieNotification
object:[remoteEnd connectionForProxy]];
selector:@selector(connectionDidDie:)
name:NSConnectionDidDieNotification
object:[remoteEnd connectionForProxy]];
}
SocketApiSocketPrivate::~SocketApiSocketPrivate()

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) by Hannah von Reth <hannah.vonreth@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; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "socketuploadjob.h"
#include "socketapi_p.h"
#include "accountmanager.h"
#include "common/syncjournaldb.h"
#include "syncengine.h"
#include <QFileInfo>
#include <QJsonArray>
#include <QRegularExpression>
using namespace OCC;
SocketUploadJob::SocketUploadJob(const QSharedPointer<SocketApiJobV2> &job)
: _apiJob(job)
{
connect(job.data(), &SocketApiJobV2::finished, this, &SocketUploadJob::deleteLater);
_localPath = _apiJob->arguments()[QLatin1String("localPath")].toString();
_remotePath = _apiJob->arguments()[QLatin1String("remotePath")].toString();
if (!_remotePath.startsWith(QLatin1Char('/'))) {
_remotePath = QLatin1Char('/') + _remotePath;
}
_pattern = job->arguments()[QLatin1String("pattern")].toString();
// TODO: use uuid
const auto accname = job->arguments()[QLatin1String("account")][QLatin1String("name")].toString();
auto account = AccountManager::instance()->account(accname);
if (!QFileInfo(_localPath).isAbsolute()) {
job->failure(QStringLiteral("Local path must be a an absolute path"));
return;
}
if (!_tmp.open()) {
job->failure(QStringLiteral("Failed to create temporary database"));
return;
}
_db = new SyncJournalDb(_tmp.fileName(), this);
_engine = new SyncEngine(account->account(), _localPath.endsWith(QLatin1Char('/')) ? _localPath : _localPath + QLatin1Char('/'), _remotePath, _db);
_engine->setParent(_db);
connect(_engine, &OCC::SyncEngine::itemCompleted, this, [this](const OCC::SyncFileItemPtr item) {
_syncedFiles.append(item->_file);
});
connect(_engine, &OCC::SyncEngine::finished, this, [this](bool ok) {
if (ok) {
_apiJob->success({ { "localPath", _localPath }, { "syncedFiles", QJsonArray::fromStringList(_syncedFiles) } });
}
});
connect(_engine, &OCC::SyncEngine::syncError, this, [this](const QString &error, ErrorCategory) {
_apiJob->failure(error);
});
}
void SocketUploadJob::start()
{
auto opt = _engine->syncOptions();
opt.setFilePattern(_pattern);
if (!opt.fileRegex().isValid()) {
_apiJob->failure(opt.fileRegex().errorString());
return;
}
_engine->setSyncOptions(opt);
// create the dir, fail if it already exists
auto mkdir = new OCC::MkColJob(_engine->account(), _remotePath);
connect(mkdir, &OCC::MkColJob::finishedWithoutError, _engine, &OCC::SyncEngine::startSync);
connect(mkdir, &OCC::MkColJob::finishedWithError, this, [this](QNetworkReply *reply) {
if (reply->error() == 202) {
_apiJob->failure(QStringLiteral("Destination %1 already exists").arg(_remotePath));
} else {
_apiJob->failure(reply->errorString());
}
});
mkdir->start();
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) by Hannah von Reth <hannah.vonreth@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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#pragma once
#include <QObject>
#include <QTemporaryFile>
#include "socketapi.h"
#include "account.h"
namespace OCC {
class SyncJournalDb;
class SyncEngine;
class SocketUploadJob : public QObject
{
Q_OBJECT
public:
SocketUploadJob(const QSharedPointer<SocketApiJobV2> &job);
void start();
private:
QSharedPointer<SocketApiJobV2> _apiJob;
QString _localPath;
QString _remotePath;
QString _pattern;
QTemporaryFile _tmp;
SyncJournalDb *_db;
SyncEngine *_engine;
QStringList _syncedFiles;
};
}

View File

@ -51,6 +51,7 @@ set(libsync_SRCS
syncfilestatustracker.cpp
localdiscoverytracker.cpp
syncresult.cpp
syncoptions.cpp
theme.cpp
clientsideencryption.cpp
clientsideencryptionjobs.cpp

View File

@ -149,11 +149,15 @@ void AbstractNetworkJob::adoptRequest(QNetworkReply *reply)
QUrl AbstractNetworkJob::makeAccountUrl(const QString &relativePath) const
{
// ensure we always used the remote folder
ASSERT(relativePath.startsWith(QLatin1Char('/')))
return Utility::concatUrlPath(_account->url(), relativePath);
}
QUrl AbstractNetworkJob::makeDavUrl(const QString &relativePath) const
{
// ensure we always used the remote folder
ASSERT(relativePath.startsWith(QLatin1Char('/')))
return Utility::concatUrlPath(_account->davUrl(), relativePath);
}

View File

@ -51,9 +51,7 @@ AccessManager::AccessManager(QObject *parent)
QByteArray AccessManager::generateRequestId()
{
// Use a UUID with the starting and ending curly brace removed.
auto uuid = QUuid::createUuid().toByteArray();
return uuid.mid(1, uuid.size() - 2);
return QUuid::createUuid().toByteArray(QUuid::WithoutBraces);
}
QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData)

View File

@ -67,8 +67,6 @@ void ProcessDirectoryJob::process()
{
ASSERT(_localQueryDone && _serverQueryDone);
QString localDir;
// Build lookup tables for local, remote and db entries.
// For suffix-virtual files, the key will normally be the base file name
// without the suffix.
@ -528,7 +526,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
}
return ParentNotChanged;
}();
processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, serverQueryMode);
return;
}
@ -545,15 +543,14 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
item->_modtime = serverEntry.modtime;
item->_size = serverEntry.size;
auto postProcessServerNew = [=] () {
auto tmp_path = path;
auto postProcessServerNew = [=]() mutable {
if (item->isDirectory()) {
_pendingAsyncJobs++;
_discoveryData->checkSelectiveSyncNewFolder(tmp_path._server, serverEntry.remotePerm,
_discoveryData->checkSelectiveSyncNewFolder(path._server, serverEntry.remotePerm,
[=](bool result) {
--_pendingAsyncJobs;
if (!result) {
processFileAnalyzeLocalInfo(item, tmp_path, localEntry, serverEntry, dbEntry, _queryServer);
processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
}
QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
});
@ -568,7 +565,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
&& !FileSystem::isExcludeFile(item->_file)) {
item->_type = ItemTypeVirtualFile;
if (isVfsWithSuffix())
addVirtualFileSuffix(tmp_path._original);
addVirtualFileSuffix(path._original);
}
if (opts._vfs->mode() != Vfs::Off && !item->_encryptedFileName.isEmpty()) {
@ -579,7 +576,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
// another scenario - we are syncing a file which is on disk but not in the database (database was removed or file was not written there yet)
item->_size = serverEntry.size - Constants::e2EeTagSize;
}
processFileAnalyzeLocalInfo(item, tmp_path, localEntry, serverEntry, dbEntry, _queryServer);
processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer);
};
// Potential NEW/NEW conflict is handled in AnalyzeLocal
@ -697,9 +694,8 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
} else {
// we need to make a request to the server to know that the original file is deleted on the server
_pendingAsyncJobs++;
auto job = new RequestEtagJob(_discoveryData->_account, originalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QString> &etag) {
auto tmp_path = path;
auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_remoteFolder + originalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QByteArray> &etag) mutable {
_pendingAsyncJobs--;
QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
if (etag || etag.error().code != 404 ||
@ -715,8 +711,8 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
// In case the deleted item was discovered in parallel
_discoveryData->findAndCancelDeletedJob(originalPath);
postProcessRename(tmp_path);
processFileFinalize(item, tmp_path, item->isDirectory(), item->_instruction == CSYNC_INSTRUCTION_RENAME ? NormalQuery : ParentDontExist, _queryServer);
postProcessRename(path);
processFileFinalize(item, path, item->isDirectory(), item->_instruction == CSYNC_INSTRUCTION_RENAME ? NormalQuery : ParentDontExist, _queryServer);
});
job->start();
done = true; // Ideally, if the origin still exist on the server, we should continue searching... but that'd be difficult
@ -1162,8 +1158,8 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
if (base.isVirtualFile() && isVfsWithSuffix())
chopVirtualFileSuffix(serverOriginalPath);
auto job = new RequestEtagJob(_discoveryData->_account, serverOriginalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QString> &etag) mutable {
if (!etag || (*etag != base._etag && !item->isDirectory()) || _discoveryData->isRenamed(originalPath)) {
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QByteArray> &etag) mutable {
if (!etag || (etag.get() != base._etag && !item->isDirectory()) || _discoveryData->isRenamed(originalPath)) {
qCInfo(lcDisco) << "Can't rename because the etag has changed or the directory is gone" << originalPath;
// Can't be a rename, leave it as a new.
postProcessLocalNew();
@ -1171,7 +1167,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
// In case the deleted item was discovered in parallel
_discoveryData->findAndCancelDeletedJob(originalPath);
processRename(path);
recurseQueryServer = *etag == base._etag ? ParentNotChanged : NormalQuery;
recurseQueryServer = etag.get() == base._etag ? ParentNotChanged : NormalQuery;
}
processFileFinalize(item, path, item->isDirectory(), NormalQuery, recurseQueryServer);
_pendingAsyncJobs--;

View File

@ -293,6 +293,6 @@ private:
signals:
void finished();
// The root etag of this directory was fetched
void etag(const QString &, const QDateTime &time);
void etag(const QByteArray &, const QDateTime &time);
};
}

View File

@ -486,7 +486,7 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(const QString &fi
//This works in concerto with the RequestEtagJob and the Folder object to check if the remote folder changed.
if (map.contains("getetag")) {
if (_firstEtag.isEmpty()) {
_firstEtag = parseEtag(map.value("getetag").toUtf8()); // for directory itself
_firstEtag = parseEtag(map.value(QStringLiteral("getetag")).toUtf8()); // for directory itself
}
}
}

View File

@ -127,7 +127,7 @@ public:
// This is not actually a network job, it is just a job
signals:
void firstDirectoryPermissions(RemotePermissions);
void etag(const QString &, const QDateTime &time);
void etag(const QByteArray &, const QDateTime &time);
void finished(const HttpResult<QVector<RemoteInfo>> &result);
private slots:
@ -141,7 +141,7 @@ private slots:
private:
QVector<RemoteInfo> _results;
QString _subPath;
QString _firstEtag;
QByteArray _firstEtag;
QByteArray _fileId;
AccountPtr _account;
// The first result is for the directory itself and need to be ignored.

View File

@ -63,9 +63,9 @@ time_t FileSystem::getModTime(const QString &filename)
&& (stat.modtime != 0)) {
result = stat.modtime;
} else {
qCWarning(lcFileSystem) << "Could not get modification time for" << filename
<< "with csync, using QFileInfo";
result = Utility::qDateTimeToTime_t(QFileInfo(filename).lastModified());
qCWarning(lcFileSystem) << "Could not get modification time for" << filename
<< "with csync, using QFileInfo:" << result;
}
return result;
}
@ -115,7 +115,7 @@ static qint64 getSizeWithCsync(const QString &filename)
if (csync_vio_local_stat(filename, &stat) != -1) {
result = stat.size;
} else {
qCWarning(lcFileSystem) << "Could not get size for" << filename << "with csync";
qCWarning(lcFileSystem) << "Could not get size for" << filename << "with csync" << Utility::formatWinError(errno);
}
return result;
}

View File

@ -32,35 +32,10 @@
#include <io.h> // for stdout
#endif
namespace OCC {
QtMessageHandler s_originalMessageHandler = nullptr;
static void mirallLogCatcher(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
{
auto logger = Logger::instance();
if (type == QtDebugMsg && !logger->logDebug()) {
if (s_originalMessageHandler) {
s_originalMessageHandler(type, ctx, message);
}
} else if (!logger->isNoop()) {
logger->doLog(qFormatLogMessage(type, ctx, message));
}
if(type == QtCriticalMsg || type == QtFatalMsg) {
std::cerr << qPrintable(qFormatLogMessage(type, ctx, message)) << std::endl;
}
if(type == QtFatalMsg) {
if (!logger->isNoop()) {
logger->close();
}
#if defined(Q_OS_WIN)
// Make application terminate in a way that can be caught by the crash reporter
Utility::crash();
#endif
}
namespace {
constexpr int CrashLogSize = 20;
}
namespace OCC {
Logger *Logger::instance()
{
@ -71,11 +46,12 @@ Logger *Logger::instance()
Logger::Logger(QObject *parent)
: QObject(parent)
{
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}");
qSetMessagePattern(QStringLiteral("%{time yyyy-MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}"));
_crashLog.resize(CrashLogSize);
#ifndef NO_MSG_HANDLER
s_originalMessageHandler = qInstallMessageHandler(mirallLogCatcher);
#else
Q_UNUSED(mirallLogCatcher)
qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &ctx, const QString &message) {
Logger::instance()->doLog(type, ctx, message);
});
#endif
}
@ -102,51 +78,38 @@ void Logger::postGuiMessage(const QString &title, const QString &message)
emit guiMessage(title, message);
}
void Logger::log(Log log)
{
QString msg;
if (_showTime) {
msg = log.timeStamp.toString(QLatin1String("MM-dd hh:mm:ss:zzz")) + QLatin1Char(' ');
}
msg += log.message;
// _logs.append(log);
// std::cout << qPrintable(log.message) << std::endl;
doLog(msg);
}
/**
* Returns true if doLog does nothing and need not to be called
*/
bool Logger::isNoop() const
{
QMutexLocker lock(&_mutex);
return !_logstream;
}
bool Logger::isLoggingToFile() const
{
QMutexLocker lock(&_mutex);
return _logstream;
}
void Logger::doLog(const QString &msg)
void Logger::doLog(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
{
const QString msg = qFormatLogMessage(type, ctx, message);
{
QMutexLocker lock(&_mutex);
_crashLogIndex = (_crashLogIndex + 1) % CrashLogSize;
_crashLog[_crashLogIndex] = msg;
if (_logstream) {
(*_logstream) << msg << endl;
if (_doFileFlush)
_logstream->flush();
}
if (type == QtFatalMsg) {
close();
#if defined(Q_OS_WIN)
// Make application terminate in a way that can be caught by the crash reporter
Utility::crash();
#endif
}
}
emit logWindowLog(msg);
}
void Logger::close()
{
QMutexLocker lock(&_mutex);
dumpCrashLog();
if (_logstream)
{
_logstream->flush();
@ -155,15 +118,6 @@ void Logger::close()
}
}
void Logger::mirallLog(const QString &message)
{
Log log_;
log_.timeStamp = QDateTime::currentDateTimeUtc();
log_.message = message;
Logger::instance()->log(log_);
}
QString Logger::logFile() const
{
return _logFile.fileName();
@ -264,7 +218,24 @@ void Logger::disableTemporaryFolderLogDir()
void Logger::setLogRules(const QSet<QString> &rules)
{
_logRules = rules;
QLoggingCategory::setFilterRules(rules.toList().join(QLatin1Char('\n')));
QString tmp;
QTextStream out(&tmp);
for (const auto &p : rules) {
out << p << QLatin1Char('\n');
}
qDebug() << tmp;
QLoggingCategory::setFilterRules(tmp);
}
void Logger::dumpCrashLog()
{
QFile logFile(QDir::tempPath() + QStringLiteral("/" APPLICATION_NAME "-crash.log"));
if (logFile.open(QFile::WriteOnly)) {
QTextStream out(&logFile);
for (int i = 1; i <= CrashLogSize; ++i) {
out << _crashLog[(_crashLogIndex + i) % CrashLogSize] << QLatin1Char('\n');
}
}
}
static bool compressLog(const QString &originalName, const QString &targetName)

View File

@ -27,12 +27,6 @@
namespace OCC {
struct Log
{
QDateTime timeStamp;
QString message;
};
/**
* @brief The Logger class
* @ingroup libsync
@ -41,16 +35,9 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject
{
Q_OBJECT
public:
bool isNoop() const;
bool isLoggingToFile() const;
void log(Log log);
void doLog(const QString &log);
void close();
static void mirallLog(const QString &message);
const QList<Log> &logs() const { return _logs; }
void doLog(QtMsgType type, const QMessageLogContext &ctx, const QString &message);
static Logger *instance();
@ -107,9 +94,11 @@ public slots:
private:
Logger(QObject *parent = nullptr);
~Logger();
QList<Log> _logs;
bool _showTime = true;
~Logger() override;
void close();
void dumpCrashLog();
QFile _logFile;
bool _doFileFlush = false;
int _logExpire = 0;
@ -119,6 +108,8 @@ private:
QString _logDirectory;
bool _temporaryFolderLogDir = false;
QSet<QString> _logRules;
QVector<QString> _crashLog;
int _crashLogIndex = 0;
};
} // namespace OCC

View File

@ -113,8 +113,8 @@ bool RequestEtagJob::finished()
if (httpCode == 207) {
// Parse DAV response
QXmlStreamReader reader(reply());
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
QString etag;
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration(QStringLiteral("d"), QStringLiteral("DAV:")));
QByteArray etag;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement && reader.namespaceUri() == QLatin1String("DAV:")) {
@ -123,9 +123,9 @@ bool RequestEtagJob::finished()
auto etagText = reader.readElementText();
auto parsedTag = parseEtag(etagText.toUtf8());
if (!parsedTag.isEmpty()) {
etag += QString::fromUtf8(parsedTag);
etag += parsedTag;
} else {
etag += etagText;
etag += etagText.toUtf8();
}
}
}
@ -182,7 +182,11 @@ bool MkColJob::finished()
qCInfo(lcMkColJob) << "MKCOL of" << reply()->request().url() << "FINISHED WITH STATUS"
<< replyStatusString();
emit finished(reply()->error());
if (reply()->error() != QNetworkReply::NoError) {
Q_EMIT finishedWithError(reply());
} else {
Q_EMIT finishedWithoutError();
}
return true;
}

View File

@ -273,9 +273,10 @@ public:
void start() override;
signals:
void finished(QNetworkReply::NetworkError);
void finishedWithError(QNetworkReply *reply);
void finishedWithoutError();
private slots:
private:
bool finished() override;
};
@ -348,8 +349,8 @@ public:
void start() override;
signals:
void etagRetrieved(const QString &etag, const QDateTime &time);
void finishedWithResult(const HttpResult<QString> &etag);
void etagRetrieved(const QByteArray &etag, const QDateTime &time);
void finishedWithResult(const HttpResult<QByteArray> &etag);
private slots:
bool finished() override;
@ -420,10 +421,10 @@ signals:
* @param statusCode - the OCS status code: 100 (!) for success
*/
void etagResponseHeaderReceived(const QByteArray &value, int statusCode);
/**
* @brief desktopNotificationStatusReceived - signal to report if notifications are allowed
* @param status - set desktop notifications allowed status
* @param status - set desktop notifications allowed status
*/
void allowDesktopNotificationsChanged(bool isAllowed);

View File

@ -40,6 +40,7 @@
#include <QTimer>
#include <QObject>
#include <QTimerEvent>
#include <QRegularExpression>
#include <qmath.h>
namespace OCC {
@ -387,7 +388,7 @@ qint64 OwncloudPropagator::smallFileSize()
return smallFileSize;
}
void OwncloudPropagator::start(const SyncFileItemVector &items)
void OwncloudPropagator::start(SyncFileItemVector &&items)
{
Q_ASSERT(std::is_sorted(items.begin(), items.end()));
@ -396,6 +397,26 @@ void OwncloudPropagator::start(const SyncFileItemVector &items)
* In order to do that we loop over the items. (which are sorted by destination)
* When we enter a directory, we can create the directory job and push it on the stack. */
const auto regex = syncOptions().fileRegex();
if (regex.isValid()) {
QSet<QStringRef> names;
for (auto &i : items) {
if (regex.match(i->_file).hasMatch()) {
int index = -1;
QStringRef ref;
do {
ref = i->_file.midRef(0, index);
names.insert(ref);
index = ref.lastIndexOf(QLatin1Char('/'));
} while (index > 0);
}
}
items.erase(std::remove_if(items.begin(), items.end(), [&names](auto i) {
return !names.contains(QStringRef { &i->_file });
}),
items.end());
}
_rootJob.reset(new PropagateRootDirectory(this));
QStack<QPair<QString /* directory name */, PropagateDirectory * /* job */>> directories;
directories.push(qMakePair(QString(), _rootJob.data()));
@ -535,7 +556,6 @@ bool OwncloudPropagator::localFileNameClash(const QString &relFile)
#ifdef Q_OS_MAC
const QFileInfo fileInfo(file);
if (!fileInfo.exists()) {
qCWarning(lcPropagator) << "No valid fileinfo";
return false;
} else {
// Need to normalize to composited form because of QTBUG-39622/QTBUG-55896

View File

@ -421,7 +421,7 @@ public:
~OwncloudPropagator();
void start(const SyncFileItemVector &_syncedItems);
void start(SyncFileItemVector &&_syncedItems);
const SyncOptions &syncOptions() const;
void setSyncOptions(const SyncOptions &syncOptions);

View File

@ -61,8 +61,7 @@ void PropagateRemoteMkdir::start()
_job = new DeleteJob(propagator()->account(),
propagator()->fullRemotePath(_item->_file),
this);
connect(static_cast<DeleteJob*>(_job.data()), &DeleteJob::finishedSignal,
this, &PropagateRemoteMkdir::slotMkdir);
connect(qobject_cast<DeleteJob *>(_job), &DeleteJob::finishedSignal, this, &PropagateRemoteMkdir::slotMkdir);
_job->start();
}
@ -76,7 +75,8 @@ void PropagateRemoteMkdir::slotStartMkcolJob()
_job = new MkColJob(propagator()->account(),
propagator()->fullRemotePath(_item->_file),
this);
connect(_job, SIGNAL(finished(QNetworkReply::NetworkError)), this, SLOT(slotMkcolJobFinished()));
connect(qobject_cast<MkColJob *>(_job), &MkColJob::finishedWithError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
connect(qobject_cast<MkColJob *>(_job), &MkColJob::finishedWithoutError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
_job->start();
}
@ -95,8 +95,8 @@ void PropagateRemoteMkdir::slotStartEncryptedMkcolJob(const QString &path, const
propagator()->fullRemotePath(filename),
{{"e2e-token", _uploadEncryptedHelper->folderToken() }},
this);
connect(job, qOverload<QNetworkReply::NetworkError>(&MkColJob::finished),
this, &PropagateRemoteMkdir::slotMkcolJobFinished);
connect(job, &MkColJob::finishedWithError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
connect(job, &MkColJob::finishedWithoutError, this, &PropagateRemoteMkdir::slotMkcolJobFinished);
_job = job;
_job->start();
}
@ -137,36 +137,34 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
return;
}
if (_item->_fileId.isEmpty()) {
// Owncloud 7.0.0 and before did not have a header with the file id.
// (https://github.com/owncloud/core/issues/9000)
// So we must get the file id using a PROPFIND
// This is required so that we can detect moves even if the folder is renamed on the server
// while files are still uploading
propagator()->_activeJobList.append(this);
auto propfindJob = new PropfindJob(propagator()->account(), jobPath, this);
propfindJob->setProperties(QList<QByteArray>() << "http://owncloud.org/ns:id");
QObject::connect(propfindJob, &PropfindJob::result, this, &PropagateRemoteMkdir::propfindResult);
QObject::connect(propfindJob, &PropfindJob::finishedWithError, this, &PropagateRemoteMkdir::propfindError);
propfindJob->start();
_job = propfindJob;
return;
}
propagator()->_activeJobList.append(this);
auto propfindJob = new PropfindJob(_job->account(), _job->path(), this);
propfindJob->setProperties({"http://owncloud.org/ns:permissions"});
connect(propfindJob, &PropfindJob::result, this, [this, jobPath](const QVariantMap &result){
propagator()->_activeJobList.removeOne(this);
_item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions")).toString());
if (!_uploadEncryptedHelper && !_item->_isEncrypted) {
success();
} else {
// We still need to mark that folder encrypted in case we were uploading it as encrypted one
// Another scenario, is we are creating a new folder because of move operation on an encrypted folder that works via remove + re-upload
propagator()->_activeJobList.append(this);
if (!_uploadEncryptedHelper && !_item->_isEncrypted) {
success();
} else {
// We still need to mark that folder encrypted in case we were uploading it as encrypted one
// Another scenario, is we are creating a new folder because of move operation on an encrypted folder that works via remove + re-upload
propagator()->_activeJobList.append(this);
// We're expecting directory path in /Foo/Bar convention...
Q_ASSERT(jobPath.startsWith('/') && !jobPath.endsWith('/'));
// But encryption job expect it in Foo/Bar/ convention
auto job = new OCC::EncryptFolderJob(propagator()->account(), propagator()->_journal, jobPath.mid(1), _item->_fileId, this);
connect(job, &OCC::EncryptFolderJob::finished, this, &PropagateRemoteMkdir::slotEncryptFolderFinished);
job->start();
}
// We're expecting directory path in /Foo/Bar convention...
Q_ASSERT(jobPath.startsWith('/') && !jobPath.endsWith('/'));
// But encryption job expect it in Foo/Bar/ convention
auto job = new OCC::EncryptFolderJob(propagator()->account(), propagator()->_journal, jobPath.mid(1), _item->_fileId, this);
connect(job, &OCC::EncryptFolderJob::finished, this, &PropagateRemoteMkdir::slotEncryptFolderFinished);
job->start();
}
});
connect(propfindJob, &PropfindJob::finishedWithError, this, [this]{
// ignore the PROPFIND error
propagator()->_activeJobList.removeOne(this);
done(SyncFileItem::NormalError);
});
propfindJob->start();
}
void PropagateRemoteMkdir::slotMkdir()
@ -235,22 +233,6 @@ void PropagateRemoteMkdir::slotEncryptFolderFinished()
success();
}
void PropagateRemoteMkdir::propfindResult(const QVariantMap &result)
{
propagator()->_activeJobList.removeOne(this);
if (result.contains("id")) {
_item->_fileId = result["id"].toByteArray();
}
success();
}
void PropagateRemoteMkdir::propfindError()
{
// ignore the PROPFIND error
propagator()->_activeJobList.removeOne(this);
done(SyncFileItem::Success);
}
void PropagateRemoteMkdir::success()
{
// Never save the etag on first mkdir.

View File

@ -54,8 +54,6 @@ private slots:
void slotStartEncryptedMkcolJob(const QString &path, const QString &filename, quint64 size);
void slotMkcolJobFinished();
void slotEncryptFolderFinished();
void propfindResult(const QVariantMap &);
void propfindError();
void success();
private:

View File

@ -418,7 +418,7 @@ private slots:
void slotPropfindFinishedWithError();
void slotPropfindIterate(const QString &name, const QMap<QString, QString> &properties);
void slotDeleteJobFinished();
void slotMkColFinished(QNetworkReply::NetworkError);
void slotMkColFinished();
void slotPutFinished();
void slotMoveJobFinished();
void slotUploadProgress(qint64, qint64);

View File

@ -249,13 +249,15 @@ void PropagateUploadFileNG::startNewUpload()
headers["OC-Total-Length"] = QByteArray::number(_fileToUpload._size);
auto job = new MkColJob(propagator()->account(), chunkUrl(), headers, this);
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)),
this, SLOT(slotMkColFinished(QNetworkReply::NetworkError)));
connect(job, &MkColJob::finishedWithError,
this, &PropagateUploadFileNG::slotMkColFinished);
connect(job, &MkColJob::finishedWithoutError,
this, &PropagateUploadFileNG::slotMkColFinished);
connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed);
job->start();
}
void PropagateUploadFileNG::slotMkColFinished(QNetworkReply::NetworkError)
void PropagateUploadFileNG::slotMkColFinished()
{
propagator()->_activeJobList.removeOne(this);
auto job = qobject_cast<MkColJob *>(sender());

View File

@ -434,7 +434,7 @@ void SyncEngine::startSync()
}
if (s_anySyncRunning || _syncRunning) {
ASSERT(false);
ASSERT(false)
return;
}
@ -452,7 +452,7 @@ void SyncEngine::startSync()
if (!QDir(_localPath).exists()) {
_anotherSyncNeeded = DelayedFollowUp;
// No _tr, it should only occur in non-mirall
syncError("Unable to find local sync folder.");
Q_EMIT syncError(QStringLiteral("Unable to find local sync folder."));
finalize(false);
return;
}
@ -465,11 +465,11 @@ void SyncEngine::startSync()
qCWarning(lcEngine()) << "Too little space available at" << _localPath << ". Have"
<< freeBytes << "bytes and require at least" << minFree << "bytes";
_anotherSyncNeeded = DelayedFollowUp;
syncError(tr("Only %1 are available, need at least %2 to start",
Q_EMIT syncError(tr("Only %1 are available, need at least %2 to start",
"Placeholders are postfixed with file sizes using Utility::octetsToString()")
.arg(
Utility::octetsToString(freeBytes),
Utility::octetsToString(minFree)));
.arg(
Utility::octetsToString(freeBytes),
Utility::octetsToString(minFree)));
finalize(false);
return;
} else {
@ -498,7 +498,7 @@ void SyncEngine::startSync()
// This creates the DB if it does not exist yet.
if (!_journal->open()) {
qCWarning(lcEngine) << "No way to create a sync journal!";
syncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."));
Q_EMIT syncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."));
finalize(false);
return;
// database creation error!
@ -514,7 +514,7 @@ void SyncEngine::startSync()
_lastLocalDiscoveryStyle = _localDiscoveryStyle;
if (_syncOptions._vfs->mode() == Vfs::WithSuffix && _syncOptions._vfs->fileSuffix().isEmpty()) {
syncError(tr("Using virtual files with suffix, but suffix is not set"));
Q_EMIT syncError(tr("Using virtual files with suffix, but suffix is not set"));
finalize(false);
return;
}
@ -526,7 +526,7 @@ void SyncEngine::startSync()
qCInfo(lcEngine) << (usingSelectiveSync ? "Using Selective Sync" : "NOT Using Selective Sync");
} else {
qCWarning(lcEngine) << "Could not retrieve selective sync list from DB";
syncError(tr("Unable to read the blacklist from the local database"));
Q_EMIT syncError(tr("Unable to read the blacklist from the local database"));
finalize(false);
return;
}
@ -557,7 +557,7 @@ void SyncEngine::startSync()
_discoveryPhase->setSelectiveSyncWhiteList(_journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok));
if (!ok) {
qCWarning(lcEngine) << "Unable to read selective sync list, aborting.";
syncError(tr("Unable to read from the sync journal."));
Q_EMIT syncError(tr("Unable to read from the sync journal."));
finalize(false);
return;
}
@ -581,7 +581,7 @@ void SyncEngine::startSync()
connect(_discoveryPhase.data(), &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered);
connect(_discoveryPhase.data(), &DiscoveryPhase::newBigFolder, this, &SyncEngine::newBigFolder);
connect(_discoveryPhase.data(), &DiscoveryPhase::fatalError, this, [this](const QString &errorString) {
syncError(errorString);
Q_EMIT syncError(errorString);
finalize(false);
});
connect(_discoveryPhase.data(), &DiscoveryPhase::finished, this, &SyncEngine::slotDiscoveryFinished);
@ -614,7 +614,7 @@ void SyncEngine::slotFolderDiscovered(bool local, const QString &folder)
emit transmissionProgress(*_progressInfo);
}
void SyncEngine::slotRootEtagReceived(const QString &e, const QDateTime &time)
void SyncEngine::slotRootEtagReceived(const QByteArray &e, const QDateTime &time)
{
if (_remoteRootEtag.isEmpty()) {
qCDebug(lcEngine) << "Root etag:" << e;
@ -640,7 +640,7 @@ void SyncEngine::slotDiscoveryFinished()
// Sanity check
if (!_journal->open()) {
qCWarning(lcEngine) << "Bailing out, DB failure";
syncError(tr("Cannot open the sync journal"));
Q_EMIT syncError(tr("Cannot open the sync journal"));
finalize(false);
return;
} else {
@ -723,10 +723,9 @@ void SyncEngine::slotDiscoveryFinished()
// Emit the started signal only after the propagator has been set up.
if (_needsUpdate)
emit(started());
Q_EMIT started();
_propagator->start(_syncItems);
_syncItems.clear();
_propagator->start(std::move(_syncItems));
qCInfo(lcEngine) << "#### Post-Reconcile end #################################################### " << _stopWatch.addLapTime(QStringLiteral("Post-Reconcile Finished")) << "ms";
};
@ -1078,7 +1077,7 @@ void SyncEngine::abort()
disconnect(_discoveryPhase.data(), nullptr, this, nullptr);
_discoveryPhase.take()->deleteLater();
syncError(tr("Aborted"));
Q_EMIT syncError(tr("Aborted"));
finalize(false);
}
}

View File

@ -138,7 +138,7 @@ public:
signals:
// During update, before reconcile
void rootEtag(const QString &, const QDateTime &);
void rootEtag(const QByteArray &, const QDateTime &);
// after the above signals. with the items that actually need propagating
void aboutToPropagate(SyncFileItemVector &);
@ -174,7 +174,7 @@ signals:
private slots:
void slotFolderDiscovered(bool local, const QString &folder);
void slotRootEtagReceived(const QString &, const QDateTime &time);
void slotRootEtagReceived(const QByteArray &, const QDateTime &time);
/** When the discovery phase discovers an item */
void slotItemDiscovered(const SyncFileItemPtr &item);
@ -234,7 +234,7 @@ private:
bool _syncRunning;
QString _localPath;
QString _remotePath;
QString _remoteRootEtag;
QByteArray _remoteRootEtag;
SyncJournalDb *_journal;
QScopedPointer<DiscoveryPhase> _discoveryPhase;
QSharedPointer<OwncloudPropagator> _propagator;

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "syncoptions.h"
#include "common/utility.h"
#include <QRegularExpression>
using namespace OCC;
SyncOptions::SyncOptions()
: _vfs(new VfsOff)
{
}
SyncOptions::~SyncOptions() = default;
void SyncOptions::fillFromEnvironmentVariables()
{
QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE");
if (!chunkSizeEnv.isEmpty())
_initialChunkSize = chunkSizeEnv.toUInt();
QByteArray minChunkSizeEnv = qgetenv("OWNCLOUD_MIN_CHUNK_SIZE");
if (!minChunkSizeEnv.isEmpty())
_minChunkSize = minChunkSizeEnv.toUInt();
QByteArray maxChunkSizeEnv = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE");
if (!maxChunkSizeEnv.isEmpty())
_maxChunkSize = maxChunkSizeEnv.toUInt();
QByteArray targetChunkUploadDurationEnv = qgetenv("OWNCLOUD_TARGET_CHUNK_UPLOAD_DURATION");
if (!targetChunkUploadDurationEnv.isEmpty())
_targetChunkUploadDuration = std::chrono::milliseconds(targetChunkUploadDurationEnv.toUInt());
int maxParallel = qgetenv("OWNCLOUD_MAX_PARALLEL").toInt();
if (maxParallel > 0)
_parallelNetworkJobs = maxParallel;
}
void SyncOptions::verifyChunkSizes()
{
_minChunkSize = qMin(_minChunkSize, _initialChunkSize);
_maxChunkSize = qMax(_maxChunkSize, _initialChunkSize);
}
QRegularExpression SyncOptions::fileRegex() const
{
return _fileRegex;
}
void SyncOptions::setFilePattern(const QString &pattern)
{
// full match or a path ending with this pattern
setPathPattern(QStringLiteral("(^|/|\\\\)") + pattern + QLatin1Char('$'));
}
void SyncOptions::setPathPattern(const QString &pattern)
{
_fileRegex.setPatternOptions(Utility::fsCasePreserving() ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption);
_fileRegex.setPattern(pattern);
}

View File

@ -15,21 +15,25 @@
#pragma once
#include "owncloudlib.h"
#include <QString>
#include <QSharedPointer>
#include <chrono>
#include "common/vfs.h"
#include <QRegularExpression>
#include <QSharedPointer>
#include <QString>
#include <chrono>
namespace OCC {
/**
* Value class containing the options given to the sync engine
*/
struct OWNCLOUDSYNC_EXPORT SyncOptions
class OWNCLOUDSYNC_EXPORT SyncOptions
{
SyncOptions()
: _vfs(new VfsOff)
{}
public:
SyncOptions();
~SyncOptions();
/** Maximum size (in Bytes) a folder can have without asking for confirmation.
* -1 means infinite */
@ -67,7 +71,45 @@ struct OWNCLOUDSYNC_EXPORT SyncOptions
/** The maximum number of active jobs in parallel */
int _parallelNetworkJobs = 6;
/** Reads settings from env vars where available.
*
* Currently reads _initialChunkSize, _minChunkSize, _maxChunkSize,
* _targetChunkUploadDuration, _parallelNetworkJobs.
*/
void fillFromEnvironmentVariables();
/** Ensure min <= initial <= max
*
* Previously min/max chunk size values didn't exist, so users might
* have setups where the chunk size exceeds the new min/max default
* values. To cope with this, adjust min/max to always include the
* initial chunk size value.
*/
void verifyChunkSizes();
/** A regular expression to match file names
* If no pattern is provided the default is an invalid regular expression.
*/
QRegularExpression fileRegex() const;
/**
* A pattern like *.txt, matching only file names
*/
void setFilePattern(const QString &pattern);
/**
* A pattern like /own.*\/.*txt matching the full path
*/
void setPathPattern(const QString &pattern);
private:
/**
* Only sync files that mathc the expression
* Invalid pattern by default.
*/
QRegularExpression _fileRegex = QRegularExpression(QStringLiteral("("));
};
}

View File

@ -468,6 +468,8 @@ QString Theme::about() const
devString += tr("<p><small>Using virtual files plugin: %1</small></p>")
.arg(Vfs::modeToString(bestAvailableVfsMode()));
devString += tr("<br>%1")
.arg(QSysInfo::productType() % QLatin1Char('-') % QSysInfo::kernelVersion());
return devString;
}

View File

@ -68,12 +68,12 @@ if (WIN32)
${CMAKE_BINARY_DIR}/src/libsync/vfs/cfapi
)
nextcloud_add_test(LongWinPath)
nextcloud_add_test(SyncCfApi)
elseif(LINUX) # elseif(LINUX OR APPLE)
nextcloud_add_test(SyncXAttr)
endif()
nextcloud_add_test(LongPath)
nextcloud_add_benchmark(LargeSync)
nextcloud_add_test(Account)

View File

@ -243,6 +243,15 @@ QString FileInfo::path() const
return (parentPath.isEmpty() ? QString() : (parentPath + QLatin1Char('/'))) + name;
}
QString FileInfo::absolutePath() const
{
if (parentPath.endsWith(QLatin1Char('/'))) {
return parentPath + name;
} else {
return parentPath + QLatin1Char('/') + name;
}
}
void FileInfo::fixupParentPathRecursively()
{
auto p = path();
@ -273,7 +282,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces
QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection);
return;
}
QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
const QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
// Don't care about the request and just return a full propfind
const QString davUri { QStringLiteral("DAV:") };
@ -288,11 +297,12 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces
auto writeFileResponse = [&](const FileInfo &fileInfo) {
xml.writeStartElement(davUri, QStringLiteral("response"));
QString url = prefix + QString::fromUtf8(QUrl::toPercentEncoding(fileInfo.path(), "/"));
auto url = QString::fromUtf8(QUrl::toPercentEncoding(fileInfo.absolutePath(), "/"));
if (!url.endsWith(QChar('/'))) {
url.append(QChar('/'));
}
xml.writeTextElement(davUri, QStringLiteral("href"), url);
const auto href = OCC::Utility::concatUrlPath(prefix, url).path();
xml.writeTextElement(davUri, QStringLiteral("href"), href);
xml.writeStartElement(davUri, QStringLiteral("propstat"));
xml.writeStartElement(davUri, QStringLiteral("prop"));
@ -488,9 +498,11 @@ FakeGetReply::FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
fileInfo = remoteRootFileInfo.find(fileName);
if (!fileInfo)
qWarning() << "Could not find file" << fileName << "on the remote";
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
if (!fileInfo) {
qDebug() << "meh;";
}
Q_ASSERT_X(fileInfo, Q_FUNC_INFO, "Could not find file on the remote");
QMetaObject::invokeMethod(this, &FakeGetReply::respond, Qt::QueuedConnection);
}
void FakeGetReply::respond()
@ -740,7 +752,7 @@ FakeErrorReply::FakeErrorReply(QNetworkAccessManager::Operation op, const QNetwo
open(QIODevice::ReadOnly);
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpErrorCode);
setError(InternalServerError, QStringLiteral("Internal Server Fake Error"));
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &FakeErrorReply::respond, Qt::QueuedConnection);
}
void FakeErrorReply::respond()

View File

@ -143,6 +143,7 @@ public:
}
QString path() const;
QString absolutePath() const;
void fixupParentPathRecursively();

View File

@ -322,7 +322,7 @@ private slots:
QCOMPARE(check_file_traversal("subdir/.sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED);
/* Other builtin excludes */
QCOMPARE(check_file_traversal("foo/Desktop.ini"), CSYNC_NOT_EXCLUDED);
QCOMPARE(check_file_traversal("foo/Desktop.ini"), CSYNC_FILE_SILENTLY_EXCLUDED);
QCOMPARE(check_file_traversal("Desktop.ini"), CSYNC_FILE_SILENTLY_EXCLUDED);
/* pattern ]*.directory - ignore and remove */

View File

@ -17,7 +17,8 @@ using namespace OCC;
// pass combination of FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE
HANDLE makeHandle(const QString &file, int shareMode)
{
const wchar_t *wuri = reinterpret_cast<const wchar_t *>(file.utf16());
const auto fName = FileSystem::longWinPath(file);
const wchar_t *wuri = reinterpret_cast<const wchar_t *>(fName.utf16());
auto handle = CreateFileW(
wuri,
GENERIC_READ | GENERIC_WRITE,
@ -39,6 +40,7 @@ class TestLockedFiles : public QObject
private slots:
void testBasicLockFileWatcher()
{
QTemporaryDir tmp;
int count = 0;
QString file;
@ -46,12 +48,16 @@ private slots:
watcher.setCheckInterval(std::chrono::milliseconds(50));
connect(&watcher, &LockWatcher::fileUnlocked, &watcher, [&](const QString &f) { ++count; file = f; });
QString tmpFile;
const QString tmpFile = tmp.path() + QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/"
"elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/"
"jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/"
"olonglonglonglong/file🐷.txt");
{
QTemporaryFile tmp;
tmp.setAutoRemove(false);
tmp.open();
tmpFile = tmp.fileName();
// use a long file path to ensure we handle that correctly
QVERIFY(QFileInfo(tmpFile).dir().mkpath("."));
QFile tmp(tmpFile);
QVERIFY(tmp.open(QFile::WriteOnly));
QVERIFY(tmp.write("ownCLoud"));
}
QVERIFY(QFile::exists(tmpFile));
@ -91,7 +97,7 @@ private slots:
QCOMPARE(file, tmpFile);
QVERIFY(!watcher.contains(tmpFile));
#endif
QFile::remove(tmpFile);
QVERIFY(tmp.remove());
}
#ifdef Q_OS_WIN

View File

@ -18,7 +18,10 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "common/filesystembase.h"
#include "csync/csync.h"
#include "csync/vio/csync_vio_local.h"
#include <QTemporaryFile>
#include <QTest>
@ -27,6 +30,7 @@ class TestLongWindowsPath : public QObject
Q_OBJECT
private Q_SLOTS:
#ifdef Q_OS_WIN
void check_long_win_path()
{
{
@ -81,7 +85,58 @@ private Q_SLOTS:
// printf( "YYYYYYYYYYYY %ld\n", strlen(new_long));
QCOMPARE(new_long.length(), 286);
}
#endif
void testLongPathStat_data()
{
QTest::addColumn<QString>("name");
QTest::newRow("long") << QStringLiteral("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/"
"elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/"
"jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/"
"olonglonglonglong/file.txt");
QTest::newRow("long emoji") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/"
"elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/"
"jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/"
"olonglonglonglong/file🐷.txt");
QTest::newRow("long russian") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/"
"elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/"
"jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/"
"olonglonglonglong/собственное.txt");
QTest::newRow("long arabic") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/"
"elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/"
"jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/"
"olonglonglonglong/السحاب.txt");
QTest::newRow("long chinese") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/"
"elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/"
"jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/"
"olonglonglonglong/自己的云.txt");
}
void testLongPathStat()
{
QTemporaryDir tmp;
QFETCH(QString, name);
const QFileInfo longPath(tmp.path() + name);
const auto data = QByteArrayLiteral("hello");
qDebug() << longPath;
QVERIFY(longPath.dir().mkpath("."));
QFile file(longPath.filePath());
QVERIFY(file.open(QFile::WriteOnly));
QVERIFY(file.write(data.constData()) == data.size());
file.close();
csync_file_stat_t buf;
QVERIFY(csync_vio_local_stat(longPath.filePath(), &buf) != -1);
QVERIFY(buf.size == data.size());
QVERIFY(buf.size == longPath.size());
QVERIFY(tmp.remove());
}
};
QTEST_GUILESS_MAIN(TestLongWindowsPath)
#include "testlongwinpath.moc"
#include "testlongpath.moc"

View File

@ -136,8 +136,6 @@ private slots:
q2.prepare("SELECT * FROM addresses");
SqlQuery q3("SELECT * FROM addresses", _db);
SqlQuery q4;
SqlQuery q5;
PreparedSqlQueryRAII testQuery(&q5, "SELECT * FROM addresses", _db);
db.reset();
}

View File

@ -336,7 +336,7 @@ private slots:
});
// For directly editing the remote checksum
auto &remoteInfo = dynamic_cast<FileInfo &>(fakeFolder.remoteModifier());
auto &remoteInfo = fakeFolder.remoteModifier();
// Base mtime with no ms content (filesystem is seconds only)
auto mtime = QDateTime::currentDateTimeUtc().addDays(-4);

View File

@ -117,7 +117,7 @@ private slots:
{
SyncJournalFileRecord record;
record._path = "foo-nochecksum";
record._remotePerm = RemotePermissions();
record._remotePerm = RemotePermissions::fromDbValue("RW");
record._modtime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc());
QVERIFY(_db.setFileRecord(record));
@ -214,6 +214,7 @@ private slots:
record._path = path;
record._type = type;
record._etag = initialEtag;
record._remotePerm = RemotePermissions::fromDbValue("RW");
_db.setFileRecord(record);
};
auto getEtag = [&](const QByteArray &path) {
@ -275,6 +276,7 @@ private slots:
auto makeEntry = [&](const QByteArray &path) {
SyncJournalFileRecord record;
record._path = path;
record._remotePerm = RemotePermissions::fromDbValue("RW");
_db.setFileRecord(record);
};