Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f0536e1
translation changes
memurats Oct 24, 2025
1da1099
added full stop
memurats Nov 10, 2025
bf311fa
added NEXT translation
memurats Nov 14, 2025
b98b4f1
Fix translation formatting in client_de.ts
memurats Jun 2, 2026
ba92f23
Fix translation formatting in client_de_NMC.ts
memurats Jun 2, 2026
2196901
Update German translation for encryption setup message
memurats Jun 2, 2026
691cf06
Update German translation for sync message
memurats Jun 3, 2026
2b8c017
fix(l10n): Update translations from Transifex
nextcloud-bot May 27, 2026
bd2cb2a
fix(l10n): Update translations from Transifex
nextcloud-bot May 28, 2026
a3300d3
fix(l10n): Update translations from Transifex
nextcloud-bot May 29, 2026
77e0f4d
fix(l10n): Update translations from Transifex
nextcloud-bot May 30, 2026
ad5ae8a
fix(l10n): Update translations from Transifex
nextcloud-bot May 31, 2026
8504066
fix(l10n): Update translations from Transifex
nextcloud-bot Jun 1, 2026
dd81f57
fix(file-provider): Updated API calls NextcloudKit dependency to brea…
i2h3 Jun 4, 2026
0e192e5
fix(file-provider): Pin NextcloudKit 7.3.3 in workspace Package.resolved
i2h3 Jun 4, 2026
974cffa
fix(networkjobs): move processEvents after reply reads in LsColJob
camilasan Jun 1, 2026
788c390
fix(l10n): Update translations from Transifex
nextcloud-bot Jun 5, 2026
7923c63
fix: do not trigger false remote wipe message
Rello Jun 4, 2026
76028c6
fix(l10n): Update translations from Transifex
nextcloud-bot Jun 6, 2026
827e3fe
fix(l10n): Update translations from Transifex
nextcloud-bot Jun 7, 2026
16316c3
fix(l10n): Update translations from Transifex
nextcloud-bot Jun 8, 2026
83509c3
translation changes
memurats Oct 24, 2025
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/nextcloud/NextcloudCapabilitiesKit.git", from: "2.5.0"),
.package(url: "https://github.com/nextcloud/NextcloudKit", from: "7.2.3"),
.package(url: "https://github.com/nextcloud/NextcloudKit", from: "7.3.3"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.55.0"),
.package(url: "https://github.com/realm/realm-swift.git", from: "20.0.4"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,17 @@ extension NextcloudKit: RemoteInterface {
requestHandler: requestHandler,
taskHandler: taskHandler,
progressHandler: progressHandler
) { account, ocId, etag, date, size, response, nkError in
) { account, response, nkError in
let allHeaderFields = response?.response?.allHeaderFields
let ocId = self.nkCommonInstance.findHeader("oc-fileid", allHeaderFields: allHeaderFields)
let etag = self.nkCommonInstance.normalizedETag(self.nkCommonInstance.findHeader("oc-etag", allHeaderFields: allHeaderFields))
let date = self.nkCommonInstance.findHeader("date", allHeaderFields: allHeaderFields)?.parsedDate(using: "EEE, dd MMM y HH:mm:ss zzz")
var size: Int64 = 0

if let value = allHeaderFields?["Content-Length"] as? String {
size = Int64(value) ?? 0
}

continuation.resume(returning: (
account,
ocId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,7 @@ public protocol RemoteInterface: Sendable {
progressHandler: @escaping (_ progress: Progress) -> Void
) async -> (
account: String,
etag: String?,
date: Date?,
length: Int64,
headers: [AnyHashable: any Sendable]?,
afError: AFError?,
response: AFDownloadResponse<URL?>?,
nkError: NKError
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public extension Item {
} else {
let identifier = NSFileProviderItemIdentifier(metadata.ocId)

let (_, _, _, _, _, _, error) = await remoteInterface.downloadAsync(
let (_, _, error) = await remoteInterface.downloadAsync(
serverUrlFileName: remotePath,
fileNameLocalPath: childLocalPath,
account: account.ncKitAccount,
Expand Down Expand Up @@ -176,7 +176,7 @@ public extension Item {
}

} else {
let (_, _, _, _, _, _, error) = await remoteInterface.downloadAsync(
let (_, _, error) = await remoteInterface.downloadAsync(
serverUrlFileName: serverUrlFileName,
fileNameLocalPath: localPath.path,
account: account.ncKitAccount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,10 @@ public struct TestableRemoteInterface: RemoteInterface, @unchecked Sendable {
progressHandler _: @escaping (_ progress: Progress) -> Void
) async -> (
account: String,
etag: String?,
date: Date?,
length: Int64,
headers: [AnyHashable: any Sendable]?,
afError: AFError?,
response: AFDownloadResponse<URL?>?,
nkError: NKError
) {
("", nil, nil, 0, nil, nil, .invalidResponseError)
("", nil, .invalidResponseError)
}

public func enumerate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,23 +1034,19 @@ public class MockRemoteInterface: RemoteInterface, @unchecked Sendable {
progressHandler _: @escaping (_ progress: Progress) -> Void = { _ in }
) async -> (
account: String,
etag: String?,
date: Date?,
length: Int64,
headers: [AnyHashable: any Sendable]?,
afError: AFError?,
response: AFDownloadResponse<URL?>?,
nkError: NKError
) {
guard let serverUrlFileName = serverUrlFileName as? String ?? (serverUrlFileName as? URL)?.absoluteString else {
return (account, nil, nil, 0, nil, nil, .urlError)
return (account, nil, .urlError)
}

guard let account = mockedAccounts[account] else {
return (account, nil, nil, 0, nil, nil, .urlError)
return (account, nil, .urlError)
}

guard let item = item(remotePath: serverUrlFileName, account: account.ncKitAccount) else {
return (account.ncKitAccount, nil, nil, 0, nil, nil, .urlError)
return (account.ncKitAccount, nil, .urlError)
}

let localUrl = URL(fileURLWithPath: fileNameLocalPath)
Expand All @@ -1066,18 +1062,10 @@ public class MockRemoteInterface: RemoteInterface, @unchecked Sendable {
}
} catch {
print("Could not write item data: \(error)")
return (account.ncKitAccount, nil, nil, 0, nil, nil, .urlError)
return (account.ncKitAccount, nil, .urlError)
}

return (
account.ncKitAccount,
item.versionIdentifier,
item.creationDate as Date,
item.size,
nil,
nil,
.success
)
return (account.ncKitAccount, nil, .success)
}

public func enumerate(
Expand Down
4 changes: 4 additions & 0 deletions src/gui/remotewipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ RemoteWipe::RemoteWipe(AccountPtr account, QObject *parent)
return;
}

if (!_account->isRemoteWipeRequested_HACK()) {
return;
}

notifyServerSuccess();
});

Expand Down
14 changes: 6 additions & 8 deletions src/libsync/networkjobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,18 +555,16 @@
connect(&parser, &LsColXMLParser::finishedWithoutError,
this, &LsColJob::finishedWithoutError);

// bool LsColXMLParser::parse takes a while, let's process some events in attempt to make UI more responsive
// from https://doc.qt.io/qt-5/qcoreapplication.html#processEvents-1
// "You can call this function occasionally when your program is busy doing a long operation (e.g. copying a file)."
// we should not abuse this function, as it affects QObject instances lifetime (when children are getting deleted or when deleteLater is called)
// one reason I had to remove ability for LsColJob to have parent, which, otherwise, leads to a crash later
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);

QString expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/dav/folder"
const auto expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/dav/folder"
if (!parser.parse(reply()->readAll(), &_folderInfos, expectedPath)) {

Check warning on line 559 in src/libsync/networkjobs.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

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

See more on https://sonarcloud.io/project/issues?id=nextmcloud_desktop&issues=AZ6nGwukWneQtRL6ORUP&open=AZ6nGwukWneQtRL6ORUP&pullRequest=429
// XML parse error
emit finishedWithError(reply());
}

// processEvents is called AFTER all reply() accesses. A pending deleteLater()
// processed inside it can zero the QPointer and caused a SIGSEGV when it was
// placed before the reply reads. Do not abuse: it affects QObject lifetimes.
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
} else {
// wrong content type, wrong HTTP code or any other network error
emit finishedWithError(reply());
Expand Down
51 changes: 51 additions & 0 deletions test/testremotediscovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@
}
};

// Reply that queues its own deletion so it fires inside LsColJob::finished()'s processEvents().
struct FakePropfindReplyWithPendingDeletion : FakePropfindReply {
FakePropfindReplyWithPendingDeletion(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op,
const QNetworkRequest &request, QObject *parent)
: FakePropfindReply(remoteRootFileInfo, op, request, parent)
{
// respond() is queued before this timer, so it fires first; the deletion fires
// inside LsColJob::finished()'s processEvents() call.
QTimer::singleShot(0, this, &QObject::deleteLater);
}
};


enum ErrorKind : int {
// Lower code are corresponding to HTML error code
Expand Down Expand Up @@ -241,6 +253,45 @@
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(rootFileIdSpy.size(), 0);
}

// Regression: processEvents() inside LsColJob::finished() must not run before reply()
// is read. A pending deleteLater() draining during processEvents() zeros the QPointer
// and caused a SIGSEGV at reply()->request().url().path() (address 0x8).
void testLsColJobDoesNotCrashWhenReplyIsDeletedDuringProcessEvents()
{
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().mkdir("B/sub");

const auto errorFolder = QStringLiteral("dav/files/admin/B");
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op,
const QNetworkRequest &req,
QIODevice *) -> QNetworkReply * {
if (req.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("PROPFIND")
&& req.url().path().endsWith(errorFolder)) {
return new FakePropfindReplyWithPendingDeletion(fakeFolder.remoteModifier(), op, req, nullptr);

Check failure on line 271 in test/testremotediscovery.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace the use of "new" with an operation that automatically manages the memory.

See more on https://sonarcloud.io/project/issues?id=nextmcloud_desktop&issues=AZ6nGw23WneQtRL6ORUQ&open=AZ6nGw23WneQtRL6ORUQ&pullRequest=429
}
return nullptr;
});

// The sync must complete without crashing. Discovery of B will fail gracefully
// because finishedWithoutError is still emitted before processEvents() runs.
QVERIFY(fakeFolder.syncOnce());
// Items under A and C must have synced normally.
QVERIFY(fakeFolder.currentLocalState().find("A"));
QVERIFY(fakeFolder.currentLocalState().find("C"));
}

// Verifies normal listing succeeds when the reply stays alive throughout.
void testLsColJobSucceedsNormally()
{
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert("A/newfile.txt");

// Normal sync with no overrides: all PROPFIND replies remain alive.
QVERIFY(fakeFolder.syncOnce());
QVERIFY(fakeFolder.currentLocalState().find("A/newfile.txt"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
};

QTEST_GUILESS_MAIN(TestRemoteDiscovery)
Expand Down
Loading