Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,10 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;

import com.nextcloud.android.sso.api.ParsedResponse;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.util.Map;

import it.niedermann.owncloud.notes.persistence.sync.OcsAPI;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import it.niedermann.owncloud.notes.shared.model.OcsResponse;
import it.niedermann.owncloud.notes.shared.model.OcsUser;
import retrofit2.Response;
import it.niedermann.owncloud.notes.util.ThrowableExtensionsKt;

@WorkerThread
public class CapabilitiesClient {
Expand All @@ -34,6 +28,8 @@ public class CapabilitiesClient {
@WorkerThread
public static Capabilities getCapabilities(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable String lastETag, @NonNull ApiProvider apiProvider) throws Throwable {
final var ocsAPI = apiProvider.getOcsAPI(context, ssoAccount);
final var repository = NotesRepository.getInstance(context);

try {
final var response = ocsAPI.getCapabilities(lastETag).blockingSingle();
final var capabilities = response.getResponse().ocs.data;
Expand All @@ -44,16 +40,20 @@ public static Capabilities getCapabilities(@NonNull Context context, @NonNull Si
Log.w(TAG, "Response headers of capabilities are null");
}

final var repository = NotesRepository.getInstance(context);
repository.insertCapabilities(capabilities);

return capabilities;
} catch (RuntimeException e) {
final var cause = e.getCause();
} catch (Throwable t) {
if (ThrowableExtensionsKt.isEmptyResponseCast(t)) {
Log.d(TAG, "Server returned empty response - Notes not modified.");
return repository.getCapabilities();
}

final var cause = t.getCause();
if (cause != null) {
throw cause;
} else {
throw e;
throw t;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil;
import it.niedermann.owncloud.notes.util.ThrowableExtensionsKt;


/**
Expand Down Expand Up @@ -280,6 +281,11 @@ private boolean pullRemoteChanges() {
return true;
} catch (Throwable t) {
final Throwable cause = t.getCause();
if (ThrowableExtensionsKt.isEmptyResponseCast(t)) {
Log.d(TAG, "Server returned empty response - Notes not modified.");
return true;
}

if (t.getClass() == RuntimeException.class && cause != null) {
if (cause.getClass() == NextcloudHttpRequestFailedException.class || cause instanceof NextcloudHttpRequestFailedException) {
final NextcloudHttpRequestFailedException httpException = (NextcloudHttpRequestFailedException) cause;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Nextcloud Notes - Android Client
*
* SPDX-FileCopyrightText: 2021-2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package it.niedermann.owncloud.notes.util

import com.nextcloud.android.sso.api.EmptyResponse

/**
* Extension function to detect a specific ClassCastException caused by an empty API response.
*
* This is used to identify cases where the API returns an empty response body,
* which leads to a `ClassCastException` when attempting to deserialize it into a non-nullable type.
*
* In particular, when the API processes an empty Reader (e.g., no changes detected),
* it may result in a `JsonSyntaxException` or similar parsing error.
* This function helps to safely ignore such cases by checking if the exception
* is a `ClassCastException` involving the `EmptyResponse` class.
*
* @return `true` if the Throwable is a ClassCastException referencing `EmptyResponse`, otherwise `false`.
*/
fun Throwable.isEmptyResponseCast(): Boolean {
return this is ClassCastException &&
(message?.contains(EmptyResponse::class.simpleName ?: "") == true)
}
Loading