mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-26 06:47:03 +03:00
#1167 Use retrofit also for capabilities endpoint
This commit is contained in:
parent
403cdecc86
commit
1c3b9a0d62
14 changed files with 174 additions and 561 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue