megalodon/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java

273 lines
7.2 KiB
Java

package org.joinmastodon.android.api;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.util.Pair;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.BaseModel;
import org.joinmastodon.android.model.Token;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import okhttp3.Call;
import okhttp3.RequestBody;
import okhttp3.Response;
public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
private static final String TAG="MastodonAPIRequest";
private String domain;
private AccountSession account;
private String path;
private String method;
private Object requestBody;
private List<Pair<String, String>> queryParams;
Class<T> respClass;
TypeToken<T> respTypeToken;
Call okhttpCall;
Token token;
boolean canceled, isRemote;
Map<String, String> headers;
long timeout;
private ProgressDialog progressDialog;
protected boolean removeUnsupportedItems;
@Nullable Context context;
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
this.path=path;
this.method=method.toString();
this.respClass=respClass;
}
public MastodonAPIRequest(HttpMethod method, String path, TypeToken<T> respTypeToken){
this.path=path;
this.method=method.toString();
this.respTypeToken=respTypeToken;
}
@Override
public synchronized void cancel(){
if(BuildConfig.DEBUG)
Log.d(TAG, "canceling request "+this);
canceled=true;
if(okhttpCall!=null){
okhttpCall.cancel();
}
}
@Override
public APIRequest<T> exec(){
throw new UnsupportedOperationException("Use exec(accountID) or execNoAuth(domain)");
}
public MastodonAPIRequest<T> exec(String accountID){
try{
account=AccountSessionManager.getInstance().getAccount(accountID);
domain=account.domain;
account.getApiController().submitRequest(this);
}catch(Exception x){
Log.e(TAG, "exec: this shouldn't happen, but it still did", x);
invokeErrorCallback(new MastodonErrorResponse(x.getLocalizedMessage(), -1, x));
}
return this;
}
public MastodonAPIRequest<T> execNoAuth(String domain){
this.domain=domain;
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
return this;
}
public MastodonAPIRequest<T> exec(String domain, Token token){
this.domain=domain;
this.token=token;
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
return this;
}
public MastodonAPIRequest<T> execRemote(String domain) {
return execRemote(domain, null);
}
public MastodonAPIRequest<T> execRemote(String domain, @Nullable AccountSession remoteSession) {
this.isRemote = true;
return Optional.ofNullable(remoteSession)
.or(() -> AccountSessionManager.getInstance().getLoggedInAccounts().stream()
.filter(acc -> acc.domain.equals(domain))
.findAny())
.map(AccountSession::getID)
.map(this::exec)
.orElseGet(() -> this.execNoAuth(domain));
}
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable){
return wrapProgress(context, message, cancelable, null);
}
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
progressDialog=new ProgressDialog(context);
progressDialog.setMessage(context.getString(message));
progressDialog.setCancelable(cancelable);
if (transform != null) transform.accept(progressDialog);
if(cancelable){
progressDialog.setOnCancelListener(dialog->cancel());
}
progressDialog.show();
return this;
}
protected void setRequestBody(Object body){
requestBody=body;
}
protected void addQueryParameter(String key, String value){
if(queryParams==null)
queryParams=new ArrayList<>();
queryParams.add(new Pair<>(key, value));
}
protected void addHeader(String key, String value){
if(headers==null)
headers=new HashMap<>();
headers.put(key, value);
}
public MastodonAPIRequest<T> setTimeout(long timeout){
this.timeout=timeout;
return this;
}
protected String getPathPrefix(){
return "/api/v1";
}
public Uri getURL(){
Uri.Builder builder=new Uri.Builder()
.scheme("https")
.authority(domain)
.path(getPathPrefix()+path);
if(queryParams!=null){
for(Pair<String, String> param:queryParams){
builder.appendQueryParameter(param.first, param.second);
}
}
return builder.build();
}
public String getMethod(){
return method;
}
public RequestBody getRequestBody() throws IOException{
if(requestBody instanceof RequestBody rb)
return rb;
return requestBody==null ? null : new JsonObjectRequestBody(requestBody);
}
@Override
public MastodonAPIRequest<T> setCallback(Callback<T> callback){
super.setCallback(callback);
return this;
}
public MastodonAPIRequest<T> setContext(Context context) {
this.context = context;
return this;
}
@Nullable
public Context getContext() {
return context;
}
@CallSuper
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
if(respObj instanceof BaseModel){
((BaseModel) respObj).isRemote = isRemote;
((BaseModel) respObj).postprocess();
}else if(respObj instanceof List){
if(removeUnsupportedItems){
Iterator<?> itr=((List<?>) respObj).iterator();
while(itr.hasNext()){
Object item=itr.next();
if(item instanceof BaseModel){
try{
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess();
}catch(ObjectValidationException x){
Log.w(TAG, "Removing invalid object from list", x);
itr.remove();
}
}
}
// no idea why we're post-processing twice, but well, as long
// as upstream does it like this, i don't wanna break anything
for(Object item:((List<?>) respObj)){
if(item instanceof BaseModel){
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess();
}
}
}else{
for(Object item:((List<?>) respObj)){
if(item instanceof BaseModel) {
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess();
}
}
}
}
}
void onError(ErrorResponse err){
if(!canceled)
invokeErrorCallback(err);
}
void onError(String msg, int httpStatus, Throwable exception){
if(!canceled)
invokeErrorCallback(new MastodonErrorResponse(msg, httpStatus, exception));
}
void onSuccess(T resp){
if(!canceled)
invokeSuccessCallback(resp);
}
@Override
protected void onRequestDone(){
if(progressDialog!=null){
progressDialog.dismiss();
}
}
public enum HttpMethod{
GET,
POST,
PUT,
DELETE,
PATCH
}
}