Skip to content

Commit aa74448

Browse files
authored
Merge pull request #5232 from nextcloud/feature/syncWithCaseClashNames
Feature/sync with case clash names
2 parents 5c42da4 + fbb0a33 commit aa74448

37 files changed

Lines changed: 2096 additions & 218 deletions

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ option(BUILD_LIBRARIES_ONLY "BUILD_LIBRARIES_ONLY" OFF)
168168
# build the GUI component, when disabled only nextcloudcmd is built
169169
option(BUILD_GUI "BUILD_GUI" ON)
170170

171+
# build the tests
172+
option(BUILD_TESTING "BUILD_TESTING" ON)
173+
171174
# When this option is enabled, 5xx errors are not added to the blacklist
172175
# Normally you don't want to enable this option because if a particular file
173176
# triggers a bug on the server, you want the file to be blacklisted.

src/common/preparedsqlquerymanager.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ class OCSYNC_EXPORT PreparedSqlQueryManager
9191
DeleteKeyValueStoreQuery,
9292
GetConflictRecordQuery,
9393
SetConflictRecordQuery,
94+
GetCaseClashConflictRecordQuery,
95+
GetCaseClashConflictRecordByPathQuery,
96+
SetCaseClashConflictRecordQuery,
97+
DeleteCaseClashConflictRecordQuery,
98+
GetAllCaseClashConflictPathQuery,
9499
DeleteConflictRecordQuery,
95100
GetRawPinStateQuery,
96101
GetEffectivePinStateQuery,

src/common/syncjournaldb.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,18 @@ bool SyncJournalDb::checkConnect()
519519
return sqlFail(QStringLiteral("Create table conflicts"), createQuery);
520520
}
521521

522+
// create the caseconflicts table.
523+
createQuery.prepare("CREATE TABLE IF NOT EXISTS caseconflicts("
524+
"path TEXT PRIMARY KEY,"
525+
"baseFileId TEXT,"
526+
"baseEtag TEXT,"
527+
"baseModtime INTEGER,"
528+
"basePath TEXT UNIQUE"
529+
");");
530+
if (!createQuery.exec()) {
531+
return sqlFail(QStringLiteral("Create table caseconflicts"), createQuery);
532+
}
533+
522534
createQuery.prepare("CREATE TABLE IF NOT EXISTS version("
523535
"major INTEGER(8),"
524536
"minor INTEGER(8),"
@@ -2201,6 +2213,101 @@ ConflictRecord SyncJournalDb::conflictRecord(const QByteArray &path)
22012213
return entry;
22022214
}
22032215

2216+
void SyncJournalDb::setCaseConflictRecord(const ConflictRecord &record)
2217+
{
2218+
QMutexLocker locker(&_mutex);
2219+
if (!checkConnect())
2220+
return;
2221+
2222+
const auto query = _queryManager.get(PreparedSqlQueryManager::SetCaseClashConflictRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO caseconflicts "
2223+
"(path, baseFileId, baseModtime, baseEtag, basePath) "
2224+
"VALUES (?1, ?2, ?3, ?4, ?5);"),
2225+
_db);
2226+
ASSERT(query)
2227+
query->bindValue(1, record.path);
2228+
query->bindValue(2, record.baseFileId);
2229+
query->bindValue(3, record.baseModtime);
2230+
query->bindValue(4, record.baseEtag);
2231+
query->bindValue(5, record.initialBasePath);
2232+
ASSERT(query->exec())
2233+
}
2234+
2235+
ConflictRecord SyncJournalDb::caseConflictRecordByBasePath(const QString &baseNamePath)
2236+
{
2237+
ConflictRecord entry;
2238+
2239+
QMutexLocker locker(&_mutex);
2240+
if (!checkConnect()) {
2241+
return entry;
2242+
}
2243+
const auto query = _queryManager.get(PreparedSqlQueryManager::GetCaseClashConflictRecordQuery, QByteArrayLiteral("SELECT path, baseFileId, baseModtime, baseEtag, basePath FROM caseconflicts WHERE basePath=?1;"), _db);
2244+
ASSERT(query)
2245+
query->bindValue(1, baseNamePath);
2246+
ASSERT(query->exec())
2247+
if (!query->next().hasData)
2248+
return entry;
2249+
2250+
entry.path = query->baValue(0);
2251+
entry.baseFileId = query->baValue(1);
2252+
entry.baseModtime = query->int64Value(2);
2253+
entry.baseEtag = query->baValue(3);
2254+
entry.initialBasePath = query->baValue(4);
2255+
return entry;
2256+
}
2257+
2258+
ConflictRecord SyncJournalDb::caseConflictRecordByPath(const QString &path)
2259+
{
2260+
ConflictRecord entry;
2261+
2262+
QMutexLocker locker(&_mutex);
2263+
if (!checkConnect()) {
2264+
return entry;
2265+
}
2266+
const auto query = _queryManager.get(PreparedSqlQueryManager::GetCaseClashConflictRecordByPathQuery, QByteArrayLiteral("SELECT path, baseFileId, baseModtime, baseEtag, basePath FROM caseconflicts WHERE path=?1;"), _db);
2267+
ASSERT(query)
2268+
query->bindValue(1, path);
2269+
ASSERT(query->exec())
2270+
if (!query->next().hasData)
2271+
return entry;
2272+
2273+
entry.path = query->baValue(0);
2274+
entry.baseFileId = query->baValue(1);
2275+
entry.baseModtime = query->int64Value(2);
2276+
entry.baseEtag = query->baValue(3);
2277+
entry.initialBasePath = query->baValue(4);
2278+
return entry;
2279+
}
2280+
2281+
void SyncJournalDb::deleteCaseClashConflictByPathRecord(const QString &path)
2282+
{
2283+
QMutexLocker locker(&_mutex);
2284+
if (!checkConnect())
2285+
return;
2286+
2287+
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteCaseClashConflictRecordQuery, QByteArrayLiteral("DELETE FROM caseconflicts WHERE path=?1;"), _db);
2288+
ASSERT(query)
2289+
query->bindValue(1, path);
2290+
ASSERT(query->exec())
2291+
}
2292+
2293+
QByteArrayList SyncJournalDb::caseClashConflictRecordPaths()
2294+
{
2295+
QMutexLocker locker(&_mutex);
2296+
if (!checkConnect()) {
2297+
return {};
2298+
}
2299+
2300+
const auto query = _queryManager.get(PreparedSqlQueryManager::GetAllCaseClashConflictPathQuery, QByteArrayLiteral("SELECT path FROM caseconflicts;"), _db);
2301+
ASSERT(query)
2302+
ASSERT(query->exec())
2303+
2304+
QByteArrayList paths;
2305+
while (query->next().hasData)
2306+
paths.append(query->baValue(0));
2307+
2308+
return paths;
2309+
}
2310+
22042311
void SyncJournalDb::deleteConflictRecord(const QByteArray &path)
22052312
{
22062313
QMutexLocker locker(&_mutex);

src/common/syncjournaldb.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,15 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
249249
/// Retrieve a conflict record by path of the file with the conflict tag
250250
ConflictRecord conflictRecord(const QByteArray &path);
251251

252+
/// Retrieve a conflict record by path of the file with the conflict tag
253+
ConflictRecord caseConflictRecordByBasePath(const QString &baseNamePath);
254+
255+
/// Retrieve a conflict record by path of the file with the conflict tag
256+
ConflictRecord caseConflictRecordByPath(const QString &path);
257+
258+
/// Return all paths of files with a conflict tag in the name and records in the db
259+
QByteArrayList caseClashConflictRecordPaths();
260+
252261
/// Delete a conflict record by path of the file with the conflict tag
253262
void deleteConflictRecord(const QByteArray &path);
254263

@@ -373,6 +382,13 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
373382
*/
374383
int autotestFailCounter = -1;
375384

385+
public slots:
386+
/// Store a new or updated record in the database
387+
void setCaseConflictRecord(const ConflictRecord &record);
388+
389+
/// Delete a case clash conflict record by path of the file with the conflict tag
390+
void deleteCaseClashConflictByPathRecord(const QString &path);
391+
376392
private:
377393
int getFileRecordCount();
378394
[[nodiscard]] bool updateDatabaseStructure();

src/common/utility.cpp

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -624,35 +624,21 @@ QString Utility::makeConflictFileName(
624624
return conflictFileName;
625625
}
626626

627-
bool Utility::isConflictFile(const char *name)
628-
{
629-
const char *bname = std::strrchr(name, '/');
630-
if (bname) {
631-
bname += 1;
632-
} else {
633-
bname = name;
634-
}
635-
636-
// Old pattern
637-
if (std::strstr(bname, "_conflict-"))
638-
return true;
639-
640-
// New pattern
641-
if (std::strstr(bname, "(conflicted copy"))
642-
return true;
643-
644-
return false;
645-
}
646-
647627
bool Utility::isConflictFile(const QString &name)
648628
{
649629
auto bname = name.midRef(name.lastIndexOf(QLatin1Char('/')) + 1);
650630

651-
if (bname.contains(QStringLiteral("_conflict-")))
631+
if (bname.contains(QStringLiteral("_conflict-"))) {
632+
return true;
633+
}
634+
635+
if (bname.contains(QStringLiteral("(conflicted copy"))) {
652636
return true;
637+
}
653638

654-
if (bname.contains(QStringLiteral("(conflicted copy")))
639+
if (isCaseClashConflictFile(name)) {
655640
return true;
641+
}
656642

657643
return false;
658644
}
@@ -722,4 +708,28 @@ QString Utility::sanitizeForFileName(const QString &name)
722708
return result;
723709
}
724710

711+
QString Utility::makeCaseClashConflictFileName(const QString &filename, const QDateTime &datetime)
712+
{
713+
auto conflictFileName(filename);
714+
// Add conflict tag before the extension.
715+
auto dotLocation = conflictFileName.lastIndexOf(QLatin1Char('.'));
716+
// If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
717+
if (dotLocation <= conflictFileName.lastIndexOf(QLatin1Char('/')) + 1) {
718+
dotLocation = conflictFileName.size();
719+
}
720+
721+
auto conflictMarker = QStringLiteral(" (case clash from ");
722+
conflictMarker += datetime.toString(QStringLiteral("yyyy-MM-dd hhmmss")) + QLatin1Char(')');
723+
724+
conflictFileName.insert(dotLocation, conflictMarker);
725+
return conflictFileName;
726+
}
727+
728+
bool Utility::isCaseClashConflictFile(const QString &name)
729+
{
730+
const auto bname = name.midRef(name.lastIndexOf(QLatin1Char('/')) + 1);
731+
732+
return bname.contains(QStringLiteral("(case clash from"));
733+
}
734+
725735
} // namespace OCC

src/common/utility.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,13 @@ namespace Utility {
223223
OCSYNC_EXPORT QString makeConflictFileName(
224224
const QString &fn, const QDateTime &dt, const QString &user);
225225

226+
OCSYNC_EXPORT QString makeCaseClashConflictFileName(const QString &filename, const QDateTime &datetime);
227+
226228
/** Returns whether a file name indicates a conflict file
227229
*/
228-
OCSYNC_EXPORT bool isConflictFile(const char *name);
230+
bool isConflictFile(const char *name) = delete;
229231
OCSYNC_EXPORT bool isConflictFile(const QString &name);
232+
OCSYNC_EXPORT bool isCaseClashConflictFile(const QString &name);
230233

231234
/** Find the base name for a conflict file name, using name pattern only
232235
*

src/csync/csync.h

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,23 @@ Q_ENUM_NS(csync_status_codes_e)
104104
* the csync state of a file.
105105
*/
106106
enum SyncInstructions {
107-
CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */
108-
CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */
109-
CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */
110-
CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */
111-
CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
112-
CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */
113-
CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */
114-
CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */
115-
CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */
116-
CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7,
117-
CSYNC_INSTRUCTION_ERROR = 1 << 8,
118-
CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE)
119-
Used when the type of something changes from directory to file
120-
or back. */
121-
CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db,
122-
but without any propagation (UPDATE|RECONCILE) */
107+
CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */
108+
CSYNC_INSTRUCTION_EVAL = 1 << 0, /* There was changed compared to the DB (UPDATE) */
109+
CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */
110+
CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */
111+
CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, /* The file is new, it is the destination of a rename (UPDATE) */
112+
CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */
113+
CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */
114+
CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */
115+
CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */
116+
CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7,
117+
CSYNC_INSTRUCTION_ERROR = 1 << 8,
118+
CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE)
119+
Used when the type of something changes from directory to file
120+
or back. */
121+
CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db,
122+
but without any propagation (UPDATE|RECONCILE) */
123+
CSYNC_INSTRUCTION_CASE_CLASH_CONFLICT = 1 << 12, /* The file need to be downloaded because it is a case clash conflict (RECONCILE) */
123124
};
124125

125126
Q_ENUM_NS(SyncInstructions)

src/csync/csync_exclude.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,14 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QString &path, bool exclu
205205
return CSYNC_FILE_SILENTLY_EXCLUDED;
206206
}
207207

208-
209-
if (excludeConflictFiles && OCC::Utility::isConflictFile(path)) {
210-
return CSYNC_FILE_EXCLUDE_CONFLICT;
208+
if (excludeConflictFiles) {
209+
if (OCC::Utility::isCaseClashConflictFile(path)) {
210+
return CSYNC_FILE_EXCLUDE_CASE_CLASH_CONFLICT;
211+
} else if (OCC::Utility::isConflictFile(path)) {
212+
return CSYNC_FILE_EXCLUDE_CONFLICT;
213+
}
211214
}
215+
212216
return CSYNC_NOT_EXCLUDED;
213217
}
214218

src/csync/csync_exclude.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ enum CSYNC_EXCLUDE_TYPE {
4343
CSYNC_FILE_EXCLUDE_HIDDEN,
4444
CSYNC_FILE_EXCLUDE_STAT_FAILED,
4545
CSYNC_FILE_EXCLUDE_CONFLICT,
46+
CSYNC_FILE_EXCLUDE_CASE_CLASH_CONFLICT,
4647
CSYNC_FILE_EXCLUDE_CANNOT_ENCODE,
4748
CSYNC_FILE_EXCLUDE_SERVER_BLACKLISTED,
4849
CSYNC_FILE_EXCLUDE_LEADING_SPACE,

src/gui/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ set(client_UI_SRCS
2929
accountsettings.ui
3030
conflictdialog.ui
3131
invalidfilenamedialog.ui
32+
caseclashfilenamedialog.ui
3233
foldercreationdialog.ui
3334
folderwizardsourcepage.ui
3435
folderwizardtargetpage.ui
@@ -73,6 +74,8 @@ set(client_SRCS
7374
application.cpp
7475
invalidfilenamedialog.h
7576
invalidfilenamedialog.cpp
77+
caseclashfilenamedialog.h
78+
caseclashfilenamedialog.cpp
7679
callstatechecker.h
7780
callstatechecker.cpp
7881
conflictdialog.h

0 commit comments

Comments
 (0)