Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions src/libsync/filesystem.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014 ownCloud GmbH
Expand All @@ -16,11 +16,14 @@
#include <QDir>
#include <QDirIterator>
#include <QCoreApplication>
#include <QRegularExpression>

#include <array>
#include <map>
#include <optional>
#include <ranges>
#include <string_view>
#include <vector>

#ifdef Q_OS_WIN
#include <securitybaseapi.h>
Expand Down Expand Up @@ -80,6 +83,96 @@
});
}

// Unlike Office (`~$…`) and LibreOffice (`.~lock.…#`) lock files, Adobe lock file
// names do not encode the guarded document's own extension, only its base name.
// The guarded document has to be located among the lock file's siblings.
// - `idlk`: InDesign documents (`indd`) and InCopy stories (`icml`).
// - `prlock`: Premiere Pro projects (`prproj`).
static const std::map<std::string_view, std::vector<std::string_view>> adobeLockFileDocumentExtensions = {

Check warning on line 91 in src/libsync/filesystem.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the redundant "static" specifier.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ9_I_RKMaC8Fe_7bjv-&open=AZ9_I_RKMaC8Fe_7bjv-&pullRequest=10402
{"idlk", {"indd", "icml"}},
{"prlock", {"prproj"}},
};

bool isAdobeLockFileExtension(const QString &path)

Check warning on line 96 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:96:6 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
const auto suffix = QFileInfo{path}.suffix().toLower().toStdString();
return adobeLockFileDocumentExtensions.contains(suffix);
}

// Returns the candidate document extensions guarded by the given Adobe lock file
// extension (lowercased, without the dot), in lookup order. Returns std::nullopt
// if the extension is not a recognised Adobe lock file extension.
std::optional<QList<QString>> adobeDocumentExtensionsFor(const QString &lockExtension)

Check warning on line 105 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:105:31 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
const auto lockExt = lockExtension.toStdString();
const auto it = adobeLockFileDocumentExtensions.find(lockExt);

Check warning on line 108 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:108:16 [readability-identifier-length]

variable name 'it' is too short, expected at least 3 characters
if (it == adobeLockFileDocumentExtensions.cend()) {
return std::nullopt;
}
QList<QString> result;

Check warning on line 112 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:112:20 [cppcoreguidelines-init-variables]

variable 'result' is not initialized
result.reserve(static_cast<int>(it->second.size()));
for (const auto &documentExtension : it->second) {
result.append(QString::fromStdString(std::string(documentExtension)));
}
return result;
}

// Parses the document base name embedded in an Adobe lock file name.
// - InDesign / InCopy (.idlk): `Test` is extracted from `~Test~0kjyv(.idlk`.
// - Premiere Pro (.prlock): `Test` is extracted from `Test.prlock`.
// Adobe lock file names drop the guarded document's own extension, so only the
// base name can be recovered here. Returns std::nullopt if \a lockExtension is
// not a recognised Adobe lock file extension, or if \a lockFileName does not
// match the naming pattern expected for it.
std::optional<QString> adobeLockFileDocumentBaseName(const QString &lockFileName, const QString &lockExtension)

Check warning on line 127 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:127:54 [bugprone-easily-swappable-parameters]

2 adjacent parameters of 'adobeLockFileDocumentBaseName' of similar type ('const int &') are easily swapped by mistake

Check warning on line 127 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:127:24 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
static const QRegularExpression idlkPattern(QStringLiteral(R"(^~(?<base>.+)~[^~]*\(\.idlk$)"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression prlockPattern(QStringLiteral(R"(^(?<base>.+)\.prlock$)"),
QRegularExpression::CaseInsensitiveOption);

const auto pattern = lockExtension == QLatin1String("idlk") ? &idlkPattern
: lockExtension == QLatin1String("prlock") ? &prlockPattern
: nullptr;

Check warning on line 136 in src/libsync/filesystem.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional operator into an independent statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ9_I_RKMaC8Fe_7bjv_&open=AZ9_I_RKMaC8Fe_7bjv_&pullRequest=10402
if (!pattern) {
return std::nullopt;
}

const auto match = pattern->match(lockFileName);
if (!match.hasMatch()) {
return std::nullopt;
}
return match.captured(QStringLiteral("base"));
}

// Resolve the document guarded by an Adobe lock file by matching a sibling file
// in the same directory by base name and expected document extension. Returns
// the guarded document's absolute file path, or an empty string if no matching
// document is found.
std::optional<QString> adobeLockFileTargetFilePath(const QString &lockFilePath)

Check warning on line 152 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:152:24 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
const QFileInfo lockFileInfo{lockFilePath};

Check warning on line 154 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:154:21 [cppcoreguidelines-init-variables]

variable 'lockFileInfo' is not initialized
const auto lockExtension = QFileInfo{lockFileInfo.fileName()}.suffix().toLower();
const auto documentExtensions = adobeDocumentExtensionsFor(lockExtension);
if (!documentExtensions || documentExtensions->isEmpty()) {
return std::nullopt;
}

const auto baseName = adobeLockFileDocumentBaseName(lockFileInfo.fileName(), lockExtension);
if (!baseName || baseName->isEmpty()) {
return std::nullopt;
}

const QDir dir = lockFileInfo.dir();

Check warning on line 166 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:166:16 [cppcoreguidelines-init-variables]

variable 'dir' is not initialized
for (const auto &documentExtension : *documentExtensions) {
const auto candidatePath = dir.absoluteFilePath(*baseName + QLatin1Char('.') + documentExtension);
if (QFileInfo::exists(candidatePath)) {
return candidatePath;
}
}
return std::nullopt;
}

// iterates through the dirPath to find the matching fileName
QString findMatchingUnlockedFileInDir(const QString &dirPath, const QString &lockFileName)
{
Expand Down Expand Up @@ -129,6 +222,14 @@
return QStringLiteral(".") + suffix;
}

// Adobe lock files (.idlk / .prlock) are identified by extension, not prefix.
const auto suffix = QFileInfo{pathSplit.last()}.suffix().toLower().toStdString();
if (adobeLockFileDocumentExtensions.contains(suffix)) {

Check warning on line 227 in src/libsync/filesystem.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "suffix" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ9_I_RKMaC8Fe_7bjwA&open=AZ9_I_RKMaC8Fe_7bjwA&pullRequest=10402
const auto pattern = QStringLiteral(".") + QString::fromStdString(suffix);
qCDebug(OCC::lcFileSystem) << "Found an Adobe lock file with extension:" << pattern << "in path:" << path;
return pattern;
}

return {};
}

Expand All @@ -144,6 +245,15 @@
return QFileInfo{path}.suffix().toLower().toStdString() == autoCADDocumentExtension;
}

bool FileSystem::isMatchingAdobeDocumentExtension(const QString &path)

Check warning on line 248 in src/libsync/filesystem.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.cpp:248:18 [modernize-use-trailing-return-type]

use a trailing return type for this function

Check warning on line 248 in src/libsync/filesystem.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ9_I_RKMaC8Fe_7bjwB&open=AZ9_I_RKMaC8Fe_7bjwB&pullRequest=10402
{
const auto pathSplit = path.split(QLatin1Char('.'));
const auto extension = pathSplit.size() > 1 ? pathSplit.last().toLower().toStdString() : std::string{};
return std::ranges::any_of(adobeLockFileDocumentExtensions, [&extension](const auto &entry) {
return std::ranges::any_of(entry.second, [&extension](const auto &documentExtension) { return documentExtension == extension; });
});
}

FileSystem::FileLockingInfo FileSystem::lockFileTargetFilePath(const QString &lockFilePath, const QString &lockFileNamePattern)
{
FileLockingInfo result;
Expand All @@ -166,6 +276,18 @@
return result;
}

// Adobe lock files (.idlk / .prlock) are resolved by sibling lookup — the lock
// file name carries only the document base name, not its extension.
if (isAdobeLockFileExtension(lockFilePath)) {
const auto adobeResolvedPath = adobeLockFileTargetFilePath(lockFilePath);
if (adobeResolvedPath) {

Check warning on line 283 in src/libsync/filesystem.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the init-statement to declare "adobeResolvedPath" inside the if statement.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ9_I_RKMaC8Fe_7bjwC&open=AZ9_I_RKMaC8Fe_7bjwC&pullRequest=10402
result.path = *adobeResolvedPath;
if (!result.path.isEmpty())
result.type = QFile::exists(lockFilePath) ? FileLockingInfo::Type::Locked : FileLockingInfo::Type::Unlocked;
}
return result;
}

if (lockFileNamePattern.isEmpty()) {
return result;
}
Expand Down
2 changes: 2 additions & 0 deletions src/libsync/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#pragma once

#include "config.h"

Check failure on line 9 in src/libsync/filesystem.h

View workflow job for this annotation

GitHub Actions / build

src/libsync/filesystem.h:9:10 [clang-diagnostic-error]

'config.h' file not found

#include "owncloudlib.h"
#include "common/filesystembase.h"
Expand Down Expand Up @@ -58,6 +58,8 @@
bool OWNCLOUDSYNC_EXPORT isMatchingOfficeFileExtension(const QString &path);
// check if it is an AutoCAD document (by .dwg extension), ONLY call it for files
bool OWNCLOUDSYNC_EXPORT isMatchingAutoCADDocumentExtension(const QString &path);
// check if it is an Adobe document guarded by an Adobe lock file (by extension), ONLY call it for files
bool OWNCLOUDSYNC_EXPORT isMatchingAdobeDocumentExtension(const QString &path);

Check warning on line 62 in src/libsync/filesystem.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this identifier to be shorter or equal to 31 characters.

See more on https://sonarcloud.io/project/issues?id=nextcloud_desktop&issues=AZ9_I_dBMaC8Fe_7bjwD&open=AZ9_I_dBMaC8Fe_7bjwD&pullRequest=10402
// finds and fetches FileLockingInfo for the corresponding file that we are locking/unlocking
FileLockingInfo OWNCLOUDSYNC_EXPORT lockFileTargetFilePath(const QString &lockFilePath, const QString &lockFileNamePattern);
// lists all files matching a lockfile pattern in dirPath
Expand Down
2 changes: 1 addition & 1 deletion src/libsync/syncengine.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014 ownCloud GmbH
Expand Down Expand Up @@ -871,7 +871,7 @@
item->_direction == SyncFileItem::Up && item->_status == SyncFileItem::Success;

if (isNewlyUploadedFile && item->_locked != SyncFileItem::LockStatus::LockedItem && _account->capabilities().filesLockAvailable() &&
(FileSystem::isMatchingOfficeFileExtension(item->_file) || FileSystem::isMatchingAutoCADDocumentExtension(item->_file))) {
(FileSystem::isMatchingOfficeFileExtension(item->_file) || FileSystem::isMatchingAutoCADDocumentExtension(item->_file) || FileSystem::isMatchingAdobeDocumentExtension(item->_file))) {
{
SyncJournalFileRecord rec;
if (!_journal->getFileRecord(item->_file, &rec) || !rec.isValid()) {
Expand Down
7 changes: 7 additions & 0 deletions sync-exclude.lst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
.~lock.*
~*.tmp

# Adobe lock files: InDesign/InCopy (.idlk) and Premiere Pro (.prlock)
# lock files. Unlike Office/LibreOffice lock files they do not encode the
# guarded document's extension, but excluding them by extension is safe as
# no legitimate user document uses it.
*.idlk
*.prlock

# AutoCAD lock files (.dwl / .dwl2) created when opening a .dwg drawing.
# Both share the document's base name and are deleted when it is closed.
*.dwl
Expand Down
12 changes: 12 additions & 0 deletions test/testexcludedfiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* any purpose.
*/

#include <QtTest>

Check failure on line 11 in test/testexcludedfiles.cpp

View workflow job for this annotation

GitHub Actions / build

test/testexcludedfiles.cpp:11:10 [clang-diagnostic-error]

'QtTest' file not found
#include <QTemporaryDir>

#include "csync_exclude.h"
Expand Down Expand Up @@ -182,6 +182,18 @@
QCOMPARE(check_file_full("my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE);
QCOMPARE(check_file_full("/a_folder/my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE);

/* Adobe lock files are excluded by extension (.idlk/.prlock) */
QCOMPARE(check_file_full("Test.idlk"), CSYNC_FILE_EXCLUDE_LIST);
QCOMPARE(check_file_full("~Test~0kjyv(.idlk"), CSYNC_FILE_EXCLUDE_LIST);
QCOMPARE(check_file_full("subdir/Test.idlk"), CSYNC_FILE_EXCLUDE_LIST);
QCOMPARE(check_file_full("Test.prlock"), CSYNC_FILE_EXCLUDE_LIST);
QCOMPARE(check_file_full("subdir/Test.prlock"), CSYNC_FILE_EXCLUDE_LIST);

/* Adobe documents themselves are NOT excluded */
QCOMPARE(check_file_full("Test.indd"), CSYNC_NOT_EXCLUDED);
QCOMPARE(check_file_full("Test.icml"), CSYNC_NOT_EXCLUDED);
QCOMPARE(check_file_full("Test.prproj"), CSYNC_NOT_EXCLUDED);

/* Not excluded because the pattern .netscape/cache requires directory. */
QCOMPARE(check_file_full(".netscape/cache"), CSYNC_NOT_EXCLUDED);

Expand Down
126 changes: 126 additions & 0 deletions test/testlockfile.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "lockfilejobs.h"

Check failure on line 6 in test/testlockfile.cpp

View workflow job for this annotation

GitHub Actions / build

test/testlockfile.cpp:6:10 [clang-diagnostic-error]

'lockfilejobs.h' file not found

#include "account.h"
#include "accountstate.h"
Expand Down Expand Up @@ -828,6 +828,33 @@
QCOMPARE(lockFileDetectedNewlyUploadedSpy.count(), 1);
}

void testLockFile_lockFile_detect_newly_uploaded_adobe_idlk()
{
// InDesign: opening a .indd creates `~{base}~{token}(.idlk`, which carries
// only the document's base name. The guarded document is resolved by
// matching a sibling .indd/.icml file in the same directory.
const auto testFileName = QStringLiteral("document.indd");
const auto testLockFileName = QStringLiteral("~document~0kjyv(.idlk");

const auto testDocumentsDirName = "documents";

FakeFolder fakeFolder{FileInfo{}};
fakeFolder.localModifier().mkdir(testDocumentsDirName);
// Adobe lock files are excluded from sync by the default exclude list;
// the FakeFolder does not load it, so replicate the exclusion here.
fakeFolder.syncEngine().excludedFiles().addManualExclude(QStringLiteral("*.idlk"));

fakeFolder.syncEngine().account()->setCapabilities({{"files", QVariantMap{{"locking", QByteArray{"1.0"}}}}});
QSignalSpy lockFileDetectedNewlyUploadedSpy(&fakeFolder.syncEngine(), &OCC::SyncEngine::lockFileDetected);

fakeFolder.localModifier().insert(testDocumentsDirName + QStringLiteral("/") + testLockFileName);
fakeFolder.localModifier().insert(testDocumentsDirName + QStringLiteral("/") + testFileName);

QVERIFY(fakeFolder.syncOnce());

QCOMPARE(lockFileDetectedNewlyUploadedSpy.count(), 1);
}

void testLockFile_autoCADLockFileTargetFilePath_resolution()
{
QTemporaryDir dir;
Expand Down Expand Up @@ -883,6 +910,105 @@
QVERIFY(orphan.path.isEmpty());
}

void testLockFile_lockFile_detect_newly_uploaded_adobe_prlock()
{
// Premiere Pro: opening a .prproj creates `{base}.prlock`.
const auto testFileName = QStringLiteral("project.prproj");
const auto testLockFileName = QStringLiteral("project.prlock");

const auto testDocumentsDirName = "documents";

FakeFolder fakeFolder{FileInfo{}};
fakeFolder.localModifier().mkdir(testDocumentsDirName);
fakeFolder.syncEngine().excludedFiles().addManualExclude(QStringLiteral("*.prlock"));

fakeFolder.syncEngine().account()->setCapabilities({{"files", QVariantMap{{"locking", QByteArray{"1.0"}}}}});
QSignalSpy lockFileDetectedNewlyUploadedSpy(&fakeFolder.syncEngine(), &OCC::SyncEngine::lockFileDetected);

fakeFolder.localModifier().insert(testDocumentsDirName + QStringLiteral("/") + testLockFileName);
fakeFolder.localModifier().insert(testDocumentsDirName + QStringLiteral("/") + testFileName);

QVERIFY(fakeFolder.syncOnce());

QCOMPARE(lockFileDetectedNewlyUploadedSpy.count(), 1);
}

void testLockFile_adobeLockFileTargetFilePath_resolution()
{
QTemporaryDir dir;
QVERIFY(dir.isValid());
const auto dirPath = dir.path() + QLatin1Char('/');

// InDesign: `~{base}~{token}(.idlk` guards `{base}.indd`.
const auto inddPath = dirPath + QStringLiteral("Test.indd");
const auto idlkPath = dirPath + QStringLiteral("~Test~0kjyv(.idlk");
QVERIFY(QFile{inddPath}.open(QIODevice::WriteOnly));
QVERIFY(QFile{idlkPath}.open(QIODevice::WriteOnly));

const auto idlkResolved = OCC::FileSystem::lockFileTargetFilePath(idlkPath, QString{});
QCOMPARE(idlkResolved.path, inddPath);
QCOMPARE(idlkResolved.type, OCC::FileSystem::FileLockingInfo::Type::Locked);

// Removing the lock file marks the document as unlocked (sibling still present).
QVERIFY(QFile::remove(idlkPath));
const auto idlkUnlocked = OCC::FileSystem::lockFileTargetFilePath(idlkPath, QString{});
QCOMPARE(idlkUnlocked.path, inddPath);
QCOMPARE(idlkUnlocked.type, OCC::FileSystem::FileLockingInfo::Type::Unlocked);

// Premiere Pro: `{base}.prlock` guards `{base}.prproj`.
const auto prprojPath = dirPath + QStringLiteral("project.prproj");
const auto prlockPath = dirPath + QStringLiteral("project.prlock");
QVERIFY(QFile{prprojPath}.open(QIODevice::WriteOnly));
QVERIFY(QFile{prlockPath}.open(QIODevice::WriteOnly));

const auto prlockResolved = OCC::FileSystem::lockFileTargetFilePath(prlockPath, QString{});
QCOMPARE(prlockResolved.path, prprojPath);
QCOMPARE(prlockResolved.type, OCC::FileSystem::FileLockingInfo::Type::Locked);

// InCopy story: `.idlk` may guard an `.icml` when no `.indd` sibling exists.
const auto icmlPath = dirPath + QStringLiteral("story.icml");
const auto idlk2Path = dirPath + QStringLiteral("~story~ab12cd(.idlk");
QVERIFY(QFile{icmlPath}.open(QIODevice::WriteOnly));
QVERIFY(QFile{idlk2Path}.open(QIODevice::WriteOnly));
const auto idlk2Resolved = OCC::FileSystem::lockFileTargetFilePath(idlk2Path, QString{});
QCOMPARE(idlk2Resolved.path, icmlPath);
QCOMPARE(idlk2Resolved.type, OCC::FileSystem::FileLockingInfo::Type::Locked);

// A non-lock file resolves to nothing.
const auto nonLock = OCC::FileSystem::lockFileTargetFilePath(dirPath + QStringLiteral("notes.txt"), QString{});
QVERIFY(nonLock.path.isEmpty());
QCOMPARE(nonLock.type, OCC::FileSystem::FileLockingInfo::Type::Unset);

// A lock file with no matching sibling document resolves to nothing.
const auto orphanIdlkPath = dirPath + QStringLiteral("~orphan~zz(.idlk");
QVERIFY(QFile{orphanIdlkPath}.open(QIODevice::WriteOnly));
const auto orphan = OCC::FileSystem::lockFileTargetFilePath(orphanIdlkPath, QString{});
QVERIFY(orphan.path.isEmpty());

// An .idlk file whose name does not match the InDesign `~base~token(.idlk`
// pattern resolves to nothing — the regex fails, base name is nullopt.
const QStringList malformedIdlkNames = {
QStringLiteral("Test.idlk"), // missing leading ~ and token
QStringLiteral("~Test.idlk"), // missing ~token(
QStringLiteral("~Test~token.idlk"), // missing trailing (
QStringLiteral("Test~token(.idlk"), // missing leading ~
};
for (const auto &malformedName : malformedIdlkNames) {
const auto malformedPath = dirPath + malformedName;
QVERIFY(QFile{malformedPath}.open(QIODevice::WriteOnly));
const auto resolved = OCC::FileSystem::lockFileTargetFilePath(malformedPath, QString{});
QVERIFY2(resolved.path.isEmpty(), qPrintable(QStringLiteral("Expected empty path for malformed idlk name: %1").arg(malformedName)));
QFile::remove(malformedPath);
}

// A non-Adobe, non-Office extension resolves to nothing.
const auto unknownExtPath = dirPath + QStringLiteral("file.unknown");
QVERIFY(QFile{unknownExtPath}.open(QIODevice::WriteOnly));
const auto unknownExt = OCC::FileSystem::lockFileTargetFilePath(unknownExtPath, QString{});
QVERIFY(unknownExt.path.isEmpty());
QCOMPARE(unknownExt.type, OCC::FileSystem::FileLockingInfo::Type::Unset);
}

void testLockFile_verifyE2eeFilesUseCorrectPath()
{
const auto e2eeRoot = QStringLiteral("encrypted");
Expand Down
Loading