#1167 Use retrofit also for capabilities endpoint

This commit is contained in:
Stefan Niedermann 2021-04-28 11:25:24 +02:00
parent 403cdecc86
commit 1c3b9a0d62
14 changed files with 174 additions and 561 deletions

View file

@ -1,87 +1,38 @@
package it.niedermann.owncloud.notes.persistence;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.api.AidlNetworkRequest;
import com.nextcloud.android.sso.api.Response;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
import com.google.gson.JsonParseException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.IOException;
import it.niedermann.owncloud.notes.persistence.sync.ApiProvider;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import retrofit2.Response;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
@WorkerThread
public class CapabilitiesClient {
private static final String TAG = CapabilitiesClient.class.getSimpleName();
private static final int MIN_NEXTCLOUD_FILES_APP_VERSION_CODE = 30090000;
protected static final String HEADER_KEY_IF_NONE_MATCH = "If-None-Match";
protected static final String HEADER_KEY_ETAG = "ETag";
private static final String API_PATH = "/ocs/v2.php/cloud/capabilities";
private static final String METHOD_GET = "GET";
private static final String PARAM_KEY_FORMAT = "format";
private static final String PARAM_VALUE_JSON = "json";
private static final Map<String, String> parameters = new HashMap<>();
static {
parameters.put(PARAM_KEY_FORMAT, PARAM_VALUE_JSON);
}
public static Capabilities getCapabilities(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable String lastETag) throws Exception {
final NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder()
.setMethod(METHOD_GET)
.setUrl(API_PATH)
.setParameter(parameters);
final Map<String, List<String>> header = new HashMap<>();
if (lastETag != null && !lastETag.isEmpty()) {
header.put(HEADER_KEY_IF_NONE_MATCH, Collections.singletonList('"' + lastETag + '"'));
requestBuilder.setHeader(header);
}
final NextcloudRequest nextcloudRequest = requestBuilder.build();
final StringBuilder result = new StringBuilder();
public static Capabilities getCapabilities(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @Nullable String lastETag) throws NextcloudHttpRequestFailedException, IOException {
final ApiProvider provider = new ApiProvider(context, ssoAccount);
final Response<Capabilities> response = provider.getOcsAPI().getCapabilities(lastETag).execute();
try {
Log.v(TAG, ssoAccount.name + "" + nextcloudRequest.getMethod() + " " + nextcloudRequest.getUrl() + " ");
final Response response = SSOClient.requestFilesApp(context.getApplicationContext(), ssoAccount, nextcloudRequest);
Log.v(TAG, "NextcloudRequest: " + nextcloudRequest.toString());
final BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
response.getBody().close();
String etag = null;
final AidlNetworkRequest.PlainHeader eTagHeader = response.getPlainHeader(HEADER_KEY_ETAG);
if (eTagHeader != null) {
etag = eTagHeader.getValue().replace("\"", "");
}
return new Capabilities(result.toString(), etag);
} catch (NullPointerException e) {
final PackageInfo pInfo = context.getApplicationContext().getPackageManager().getPackageInfo("com.nextcloud.client", 0);
if (pInfo.versionCode < MIN_NEXTCLOUD_FILES_APP_VERSION_CODE) {
throw new NextcloudFilesAppNotSupportedException();
final Capabilities capabilities = response.body();
capabilities.setETag(response.headers().get(HEADER_KEY_ETAG));
return capabilities;
} catch (JsonParseException e) {
if (e.getCause() instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e.getCause()).getStatusCode() == HTTP_UNAVAILABLE) {
throw (NextcloudHttpRequestFailedException) e.getCause();
} else {
throw e;
}

View file

@ -1,217 +0,0 @@
package it.niedermann.owncloud.notes.persistence;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.api.AidlNetworkRequest;
import com.nextcloud.android.sso.api.Response;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.ApiVersion;
import it.niedermann.owncloud.notes.shared.model.ServerResponse.NoteResponse;
import it.niedermann.owncloud.notes.shared.model.ServerResponse.NotesResponse;
@SuppressWarnings("WeakerAccess")
@WorkerThread
public abstract class NotesClient {
final static int MIN_NEXTCLOUD_FILES_APP_VERSION_CODE = 30090000;
private static final String TAG = NotesClient.class.getSimpleName();
protected final Context appContext;
protected static final String GET_PARAM_KEY_PRUNE_BEFORE = "pruneBefore";
protected static final String HEADER_KEY_ETAG = "ETag";
protected static final String HEADER_KEY_LAST_MODIFIED = "Last-Modified";
protected static final String HEADER_KEY_CONTENT_TYPE = "Content-Type";
protected static final String HEADER_KEY_IF_NONE_MATCH = "If-None-Match";
protected static final String HEADER_KEY_X_NOTES_API_VERSIONS = "X-Notes-API-Versions";
protected static final String HEADER_VALUE_APPLICATION_JSON = "application/json";
protected static final String METHOD_GET = "GET";
protected static final String METHOD_PUT = "PUT";
protected static final String METHOD_POST = "POST";
protected static final String METHOD_DELETE = "DELETE";
public static final String JSON_ID = "id";
public static final String JSON_TITLE = "title";
public static final String JSON_CONTENT = "content";
public static final String JSON_FAVORITE = "favorite";
public static final String JSON_CATEGORY = "category";
public static final String JSON_ETAG = "etag";
public static final String JSON_MODIFIED = "modified";
public static final ApiVersion[] SUPPORTED_API_VERSIONS = new ApiVersion[]{
new ApiVersion(1, 0),
new ApiVersion(0, 2)
};
public static NotesClient newInstance(@Nullable ApiVersion preferredApiVersion,
@NonNull Context appContext) {
if (preferredApiVersion == null) {
Log.i(TAG, "apiVersion is null, using " + NotesClientV02.class.getSimpleName());
return new NotesClientV02(appContext);
} else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[0]) == 0) {
Log.i(TAG, "Using " + NotesClientV1.class.getSimpleName());
return new NotesClientV1(appContext);
} else if (preferredApiVersion.compareTo(SUPPORTED_API_VERSIONS[1]) == 0) {
Log.i(TAG, "Using " + NotesClientV02.class.getSimpleName());
return new NotesClientV02(appContext);
}
Log.w(TAG, "Unsupported API version " + preferredApiVersion + " - try using " + NotesClientV02.class.getSimpleName());
return new NotesClientV02(appContext);
}
@SuppressWarnings("WeakerAccess")
protected NotesClient(@NonNull Context appContext) {
this.appContext = appContext;
}
/**
* Gets the list of notes from the server.
*
* @param ssoAccount Account to be used
* @param lastModified Last modified time of a former response (Unix timestamp in seconds!). All notes older than this time will be skipped.
* @param lastETag ETag of a former response. If nothing changed, the response will be 304 NOT MODIFIED.
* @return list of notes
* @throws Exception
*/
abstract NotesResponse getNotes(SingleSignOnAccount ssoAccount, Calendar lastModified, String lastETag) throws Exception;
abstract NoteResponse createNote(SingleSignOnAccount ssoAccount, Note note) throws Exception;
abstract NoteResponse editNote(SingleSignOnAccount ssoAccount, Note note) throws Exception;
abstract void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception;
/**
* This entity class is used to return relevant data of the HTTP reponse.
*/
public static class ResponseData {
private final String content;
private final String etag;
private final String supportedApiVersions;
private final Calendar lastModified;
ResponseData(@NonNull String content, String etag, @NonNull Calendar lastModified, @Nullable String supportedApiVersions) {
this.content = content;
this.etag = etag;
this.lastModified = lastModified;
this.supportedApiVersions = supportedApiVersions;
}
public String getContent() {
return content;
}
public String getETag() {
return etag;
}
public Calendar getLastModified() {
return lastModified;
}
public String getSupportedApiVersions() {
return this.supportedApiVersions;
}
}
abstract protected String getApiPath();
/**
* Request-Method for POST, PUT with or without JSON-Object-Parameter
*
* @param target Filepath to the wanted function
* @param method GET, POST, DELETE or PUT
* @param parameter optional headers to be sent
* @param requestBody JSON Object which shall be transferred to the server.
* @param lastETag optional ETag of last response
* @return Body of answer
*/
protected ResponseData requestServer(SingleSignOnAccount ssoAccount, String target, String method, Map<String, String> parameter, JSONObject requestBody, String lastETag) throws Exception {
final NextcloudRequest.Builder requestBuilder = new NextcloudRequest.Builder()
.setMethod(method)
.setUrl(getApiPath() + target);
if (parameter != null) {
requestBuilder.setParameter(parameter);
}
final Map<String, List<String>> header = new HashMap<>();
if (requestBody != null) {
header.put(HEADER_KEY_CONTENT_TYPE, Collections.singletonList(HEADER_VALUE_APPLICATION_JSON));
requestBuilder.setRequestBody(requestBody.toString());
}
if (lastETag != null && !lastETag.isEmpty() && METHOD_GET.equals(method)) {
header.put(HEADER_KEY_IF_NONE_MATCH, Collections.singletonList('"' + lastETag + '"'));
requestBuilder.setHeader(header);
}
final NextcloudRequest nextcloudRequest = requestBuilder.build();
final StringBuilder result = new StringBuilder();
try {
Log.v(TAG, ssoAccount.name + "" + nextcloudRequest.getMethod() + " " + nextcloudRequest.getUrl() + " ");
Log.d(TAG, "NextcloudRequest: " + nextcloudRequest.toString());
final Response response = SSOClient.requestFilesApp(appContext, ssoAccount, nextcloudRequest);
final BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
response.getBody().close();
String etag = "";
final AidlNetworkRequest.PlainHeader eTagHeader = response.getPlainHeader(HEADER_KEY_ETAG);
if (eTagHeader != null) {
etag = Objects.requireNonNull(eTagHeader.getValue()).replace("\"", "");
}
final Calendar lastModified = Calendar.getInstance();
lastModified.setTimeInMillis(0);
final AidlNetworkRequest.PlainHeader lastModifiedHeader = response.getPlainHeader(HEADER_KEY_LAST_MODIFIED);
if (lastModifiedHeader != null)
lastModified.setTimeInMillis(Date.parse(lastModifiedHeader.getValue()));
Log.d(TAG, "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + lastModified + ")");
String supportedApiVersions = null;
final AidlNetworkRequest.PlainHeader supportedApiVersionsHeader = response.getPlainHeader(HEADER_KEY_X_NOTES_API_VERSIONS);
if (supportedApiVersionsHeader != null) {
supportedApiVersions = "[" + Objects.requireNonNull(supportedApiVersionsHeader.getValue()) + "]";
}
// return these header fields since they should only be saved after successful processing the result!
return new ResponseData(result.toString(), etag, lastModified, supportedApiVersions);
} catch (NullPointerException e) {
final PackageInfo pInfo = appContext.getPackageManager().getPackageInfo("com.nextcloud.client", 0);
if (pInfo.versionCode < MIN_NEXTCLOUD_FILES_APP_VERSION_CODE) {
throw new NextcloudFilesAppNotSupportedException();
} else {
throw e;
}
}
}
}

View file

@ -1,63 +0,0 @@
package it.niedermann.owncloud.notes.persistence;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import org.json.JSONObject;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.ServerResponse.NoteResponse;
import it.niedermann.owncloud.notes.shared.model.ServerResponse.NotesResponse;
@WorkerThread
public class NotesClientV02 extends NotesClient {
private static final String API_PATH = "/index.php/apps/notes/api/v0.2/";
NotesClientV02(@NonNull Context appContext) {
super(appContext);
}
NotesResponse getNotes(SingleSignOnAccount ssoAccount, Calendar lastModified, String lastETag) throws Exception {
final Map<String, String> parameter = new HashMap<>();
parameter.put(GET_PARAM_KEY_PRUNE_BEFORE, Long.toString(lastModified == null ? 0 : lastModified.getTimeInMillis() / 1_000));
return new NotesResponse(requestServer(ssoAccount, "notes", METHOD_GET, parameter, null, lastETag));
}
private NoteResponse putNote(SingleSignOnAccount ssoAccount, Note note, String path, String method) throws Exception {
JSONObject paramObject = new JSONObject();
paramObject.accumulate(JSON_CONTENT, note.getContent());
paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1_000);
paramObject.accumulate(JSON_FAVORITE, note.getFavorite());
paramObject.accumulate(JSON_CATEGORY, note.getCategory());
return new NoteResponse(requestServer(ssoAccount, path, method, null, paramObject, null));
}
@Override
NoteResponse createNote(SingleSignOnAccount ssoAccount, Note note) throws Exception {
return putNote(ssoAccount, note, "notes", METHOD_POST);
}
@Override
NoteResponse editNote(SingleSignOnAccount ssoAccount, Note note) throws Exception {
return putNote(ssoAccount, note, "notes/" + note.getRemoteId(), METHOD_PUT);
}
@Override
void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception {
this.requestServer(ssoAccount, "notes/" + noteId, METHOD_DELETE, null, null, null);
}
@Override
protected String getApiPath() {
return API_PATH;
}
}

View file

@ -1,64 +0,0 @@
package it.niedermann.owncloud.notes.persistence;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import org.json.JSONObject;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.ServerResponse.NoteResponse;
import it.niedermann.owncloud.notes.shared.model.ServerResponse.NotesResponse;
@WorkerThread
public class NotesClientV1 extends NotesClient {
private static final String API_PATH = "/index.php/apps/notes/api/v1/";
NotesClientV1(@NonNull Context appContext) {
super(appContext);
}
NotesResponse getNotes(SingleSignOnAccount ssoAccount, Calendar lastModified, String lastETag) throws Exception {
final Map<String, String> parameter = new HashMap<>();
parameter.put(GET_PARAM_KEY_PRUNE_BEFORE, Long.toString(lastModified == null ? 0 : lastModified.getTimeInMillis() / 1_000));
return new NotesResponse(requestServer(ssoAccount, "notes", METHOD_GET, parameter, null, lastETag));
}
private NoteResponse putNote(SingleSignOnAccount ssoAccount, Note note, String path, String method) throws Exception {
JSONObject paramObject = new JSONObject();
paramObject.accumulate(JSON_TITLE, note.getTitle());
paramObject.accumulate(JSON_CONTENT, note.getContent());
paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1_000);
paramObject.accumulate(JSON_FAVORITE, note.getFavorite());
paramObject.accumulate(JSON_CATEGORY, note.getCategory());
return new NoteResponse(requestServer(ssoAccount, path, method, null, paramObject, null));
}
@Override
NoteResponse createNote(SingleSignOnAccount ssoAccount, Note note) throws Exception {
return putNote(ssoAccount, note, "notes", METHOD_POST);
}
@Override
NoteResponse editNote(SingleSignOnAccount ssoAccount, Note note) throws Exception {
return putNote(ssoAccount, note, "notes/" + note.getRemoteId(), METHOD_PUT);
}
@Override
void deleteNote(SingleSignOnAccount ssoAccount, long noteId) throws Exception {
this.requestServer(ssoAccount, "notes/" + noteId, METHOD_DELETE, null, null, null);
}
@Override
protected String getApiPath() {
return API_PATH;
}
}

View file

@ -1,24 +1,40 @@
package it.niedermann.owncloud.notes.persistence;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.bumptech.glide.load.HttpException;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;
import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.api.NextcloudAPI;
import com.nextcloud.android.sso.api.Response;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Type;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.owncloud.notes.persistence.sync.CapabilitiesDeserializer;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
@SuppressWarnings("WeakerAccess")
@WorkerThread
public class SSOClient {
@ -27,10 +43,6 @@ public class SSOClient {
private static final Map<String, NextcloudAPI> mNextcloudAPIs = new HashMap<>();
public static Response requestFilesApp(@NonNull Context context, @NonNull SingleSignOnAccount ssoAccount, @NonNull NextcloudRequest nextcloudRequest) throws Exception {
return getNextcloudAPI(context.getApplicationContext(), ssoAccount).performNetworkRequestV2(nextcloudRequest);
}
public static NextcloudAPI getNextcloudAPI(Context appContext, SingleSignOnAccount ssoAccount) {
if (mNextcloudAPIs.containsKey(ssoAccount.name)) {
return mNextcloudAPIs.get(ssoAccount.name);
@ -45,6 +57,7 @@ public class SSOClient {
calendar.setTimeInMillis(src.getAsLong() * 1_000);
return calendar;
})
.registerTypeAdapter(Capabilities.class, new CapabilitiesDeserializer())
.create(),
new NextcloudAPI.ApiConnectedListener() {
@Override

View file

@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@ -21,7 +20,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.NoSuchElementException;
import it.niedermann.owncloud.notes.persistence.NotesClient;
import it.niedermann.owncloud.notes.shared.model.ApiVersion;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
@ -83,7 +81,7 @@ public class Account implements Serializable {
final Collection<ApiVersion> supportedApiVersions = new HashSet<>(versionsArray.length());
for (int i = 0; i < versionsArray.length(); i++) {
final ApiVersion parsedApiVersion = ApiVersion.of(versionsArray.getString(i));
for (ApiVersion temp : NotesClient.SUPPORTED_API_VERSIONS) {
for (ApiVersion temp : ApiVersion.SUPPORTED_API_VERSIONS) {
if (temp.compareTo(parsedApiVersion) == 0) {
supportedApiVersions.add(parsedApiVersion);
break;

View file

@ -16,16 +16,23 @@ import retrofit2.NextcloudRetrofitApiBuilder;
*/
public class ApiProvider {
private static final String API_ENDPOINT_OCS = "/ocs/v2.php/cloud/";
private static final String API_ENDPOINT_NOTES = "/index.php/apps/notes/api/v1/";
private final OcsAPI ocsAPI;
private final NotesAPI notesAPI;
public ApiProvider(@NonNull Context appContext, @NonNull SingleSignOnAccount ssoAccount) {
final NextcloudAPI nextcloudAPI = SSOClient.getNextcloudAPI(appContext, ssoAccount);
ocsAPI = new NextcloudRetrofitApiBuilder(nextcloudAPI, API_ENDPOINT_OCS).create(OcsAPI.class);
notesAPI = new NextcloudRetrofitApiBuilder(nextcloudAPI, API_ENDPOINT_NOTES).create(NotesAPI.class);
}
public NotesAPI getNotesAPI() {
return notesAPI;
}
public OcsAPI getOcsAPI() {
return ocsAPI;
}
}

View file

@ -0,0 +1,81 @@
package it.niedermann.owncloud.notes.persistence.sync;
import android.graphics.Color;
import android.util.Log;
import com.bumptech.glide.load.HttpException;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.lang.reflect.Type;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
public class CapabilitiesDeserializer implements JsonDeserializer<Capabilities> {
private static final String TAG = CapabilitiesDeserializer.class.getSimpleName();
private static final String JSON_OCS = "ocs";
private static final String JSON_OCS_META = "meta";
private static final String JSON_OCS_META_STATUSCODE = "statuscode";
private static final String JSON_OCS_DATA = "data";
private static final String JSON_OCS_DATA_CAPABILITIES = "capabilities";
private static final String JSON_OCS_DATA_CAPABILITIES_NOTES = "notes";
private static final String JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION = "api_version";
private static final String JSON_OCS_DATA_CAPABILITIES_THEMING = "theming";
private static final String JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR = "color";
private static final String JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT = "color-text";
@Override
public Capabilities deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final Capabilities response = new Capabilities();
final JsonObject ocs = json.getAsJsonObject().getAsJsonObject(JSON_OCS);
if (ocs.has(JSON_OCS_META)) {
final JsonObject meta = ocs.getAsJsonObject(JSON_OCS_META);
if (meta.has(JSON_OCS_META_STATUSCODE)) {
if (meta.get(JSON_OCS_META_STATUSCODE).getAsInt() == HTTP_UNAVAILABLE) {
Log.i(TAG, "Capabilities Endpoint: This instance is currently in maintenance mode.");
throw new JsonParseException(new NextcloudHttpRequestFailedException(HTTP_UNAVAILABLE, new HttpException(HTTP_UNAVAILABLE)));
}
}
}
if (ocs.has(JSON_OCS_DATA)) {
final JsonObject data = ocs.getAsJsonObject(JSON_OCS_DATA);
if (data.has(JSON_OCS_DATA_CAPABILITIES)) {
final JsonObject capabilities = data.getAsJsonObject(JSON_OCS_DATA_CAPABILITIES);
if (capabilities.has(JSON_OCS_DATA_CAPABILITIES_NOTES)) {
final JsonObject notes = capabilities.getAsJsonObject(JSON_OCS_DATA_CAPABILITIES_NOTES);
if (notes.has(JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION)) {
final JsonElement apiVersion = notes.get(JSON_OCS_DATA_CAPABILITIES_NOTES_API_VERSION);
response.setApiVersion(apiVersion.isJsonArray() ? apiVersion.toString() : null);
}
}
if (capabilities.has(JSON_OCS_DATA_CAPABILITIES_THEMING)) {
final JsonObject theming = capabilities.getAsJsonObject(JSON_OCS_DATA_CAPABILITIES_THEMING);
if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR)) {
try {
response.setColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.get(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR).getAsString())));
} catch (Exception e) {
e.printStackTrace();
}
}
if (theming.has(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT)) {
try {
response.setTextColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(theming.get(JSON_OCS_DATA_CAPABILITIES_THEMING_COLOR_TEXT).getAsString())));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
return response;
}
}

View file

@ -1,33 +0,0 @@
package it.niedermann.owncloud.notes.persistence.sync;
import java.util.List;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
/**
* @link <a href="https://deck.readthedocs.io/en/latest/API/">Deck REST API</a>
*/
public interface NextcloudAPI {
@GET("notes")
Call<List<Note>> getNotes(@Query(value = "pruneBefore") long lastModified, @Query("If-None-Match") String lastETag);
@POST("notes")
Call<Note> createNote(@Body Note note);
@PUT("notes/{remoteId}")
Call<Note> editNote(@Body Note note, @Path("remoteId") long remoteId);
@DELETE("notes/{remoteId}")
Call<Note> deleteNote(@Path("remoteId") long noteId);
}

View file

@ -0,0 +1,16 @@
package it.niedermann.owncloud.notes.persistence.sync;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* @link <a href="https://deck.readthedocs.io/en/latest/API/">Deck REST API</a>
*/
public interface OcsAPI {
@GET("capabilities?format=json")
Call<Capabilities> getCapabilities(@Query("If-None-Match") String eTag);
}

View file

@ -10,6 +10,11 @@ import java.util.regex.Pattern;
public class ApiVersion implements Comparable<ApiVersion> {
private static final Pattern NUMBER_EXTRACTION_PATTERN = Pattern.compile("[0-9]+");
public static final ApiVersion[] SUPPORTED_API_VERSIONS = new ApiVersion[]{
new ApiVersion(1, 0),
new ApiVersion(0, 2)
};
private String originalVersion = "?";
private int major;
private int minor;

View file

@ -6,6 +6,7 @@ import android.util.Log;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.bumptech.glide.load.HttpException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
@ -38,12 +39,17 @@ public class Capabilities {
private String apiVersion = null;
@ColorInt
private Integer color = -16743735;
private int color = -16743735;
@ColorInt
private Integer textColor = -16777216;
private int textColor = -16777216;
@Nullable
private final String eTag;
private String eTag;
public Capabilities() {
}
@VisibleForTesting
public Capabilities(@NonNull String response, @Nullable String eTag) throws NextcloudHttpRequestFailedException {
this.eTag = eTag;
final JSONObject ocs;
@ -92,6 +98,10 @@ public class Capabilities {
}
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getApiVersion() {
return apiVersion;
}
@ -101,14 +111,26 @@ public class Capabilities {
return eTag;
}
public Integer getColor() {
public void setETag(@Nullable String eTag) {
this.eTag = eTag;
}
public int getColor() {
return color;
}
public Integer getTextColor() {
public void setColor(@ColorInt int color) {
this.color = color;
}
public int getTextColor() {
return textColor;
}
public void setTextColor(@ColorInt int textColor) {
this.textColor = textColor;
}
@NonNull
@Override
public String toString() {

View file

@ -1,103 +0,0 @@
package it.niedermann.owncloud.notes.shared.model;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import it.niedermann.owncloud.notes.persistence.NotesClient;
import it.niedermann.owncloud.notes.persistence.entity.Note;
/**
* Provides entity classes for handling server responses with a single note ({@link NoteResponse}) or a list of notes ({@link NotesResponse}).
*/
public class ServerResponse {
public static class NoteResponse extends ServerResponse {
public NoteResponse(NotesClient.ResponseData response) {
super(response);
}
public Note getNote() throws JSONException {
return getNoteFromJSON(new JSONObject(getContent()));
}
}
public static class NotesResponse extends ServerResponse {
public NotesResponse(NotesClient.ResponseData response) {
super(response);
}
public List<Note> getNotes() throws JSONException {
List<Note> notesList = new ArrayList<>();
JSONArray notes = new JSONArray(getContent());
for (int i = 0; i < notes.length(); i++) {
JSONObject json = notes.getJSONObject(i);
notesList.add(getNoteFromJSON(json));
}
return notesList;
}
}
private final NotesClient.ResponseData response;
ServerResponse(NotesClient.ResponseData response) {
this.response = response;
}
protected String getContent() {
return response == null ? null : response.getContent();
}
public String getETag() {
return response.getETag();
}
public Calendar getLastModified() {
return response.getLastModified();
}
@Nullable
public String getSupportedApiVersions() {
return response.getSupportedApiVersions();
}
Note getNoteFromJSON(JSONObject json) throws JSONException {
long remoteId = 0;
String title = "";
String content = "";
Calendar modified = null;
boolean favorite = false;
String category = "";
String etag = null;
if (!json.isNull(NotesClient.JSON_ID)) {
remoteId = json.getLong(NotesClient.JSON_ID);
}
if (!json.isNull(NotesClient.JSON_TITLE)) {
title = json.getString(NotesClient.JSON_TITLE);
}
if (!json.isNull(NotesClient.JSON_CONTENT)) {
content = json.getString(NotesClient.JSON_CONTENT);
}
if (!json.isNull(NotesClient.JSON_MODIFIED)) {
modified = Calendar.getInstance();
modified.setTimeInMillis(json.getLong(NotesClient.JSON_MODIFIED) * 1_000);
}
if (!json.isNull(NotesClient.JSON_FAVORITE)) {
favorite = json.getBoolean(NotesClient.JSON_FAVORITE);
}
if (!json.isNull(NotesClient.JSON_CATEGORY)) {
category = json.getString(NotesClient.JSON_CATEGORY);
}
if (!json.isNull(NotesClient.JSON_ETAG)) {
etag = json.getString(NotesClient.JSON_ETAG);
}
return new Note(remoteId, modified, title, content, category, favorite, etag);
}
}

View file

@ -48,8 +48,8 @@ public class CapabilitiesTest {
final Capabilities capabilities = new Capabilities(response, null);
assertNull(capabilities.getETag());
assertNull(capabilities.getApiVersion());
assertEquals(Integer.valueOf(Color.parseColor("#1E4164")), capabilities.getColor());
assertEquals(Integer.valueOf(Color.parseColor("#ffffff")), capabilities.getTextColor());
assertEquals(Color.parseColor("#1E4164"), capabilities.getColor());
assertEquals(Color.parseColor("#ffffff"), capabilities.getTextColor());
}
@Test
@ -86,8 +86,8 @@ public class CapabilitiesTest {
final Capabilities capabilities = new Capabilities(response, null);
assertNull(capabilities.getETag());
assertEquals("1.0", capabilities.getApiVersion());
assertEquals(Integer.valueOf(Color.parseColor("#1E4164")), capabilities.getColor());
assertEquals(Integer.valueOf(Color.parseColor("#ffffff")), capabilities.getTextColor());
assertEquals(Color.parseColor("#1E4164"), capabilities.getColor());
assertEquals(Color.parseColor("#ffffff"), capabilities.getTextColor());
}
@Test