fixed image upload

This commit is contained in:
Mariotaku Lee 2015-05-12 01:52:57 +08:00
parent 2342598960
commit 2a75fa15db
7 changed files with 269 additions and 146 deletions

View File

@ -1,12 +1,13 @@
package org.mariotaku.simplerestapi;
import org.mariotaku.simplerestapi.http.mime.BaseTypedData;
import org.mariotaku.simplerestapi.http.mime.TypedData;
import org.mariotaku.simplerestapi.param.File;
/**
* Created by mariotaku on 15/2/6.
*/
class FileValue {
public final class FileValue {
private final File annotation;
private final Object value;
@ -24,7 +25,6 @@ class FileValue {
}
public TypedData body() {
//TODO implement file to TypedData
throw new UnsupportedOperationException();
return BaseTypedData.wrap(value);
}
}

View File

@ -19,9 +19,13 @@
package org.mariotaku.simplerestapi;
import android.support.annotation.Nullable;
import android.util.Pair;
import org.mariotaku.simplerestapi.http.mime.FormTypedBody;
import org.mariotaku.simplerestapi.http.mime.MultipartTypedBody;
import org.mariotaku.simplerestapi.http.mime.TypedData;
import org.mariotaku.simplerestapi.param.Body;
import java.util.List;
import java.util.Map;
@ -31,17 +35,21 @@ import java.util.Map;
*/
public final class RequestInfo {
private String method;
private String path;
private List<Pair<String, String>> queries, forms, headers;
private List<Pair<String, TypedData>> parts;
private Map<String, Object> extras;
private TypedData body;
private FileValue file;
private Body body;
private TypedData bodyCache;
public RequestInfo(String method, String path, List<Pair<String, String>> queries,
List<Pair<String, String>> forms, List<Pair<String, String>> headers,
List<Pair<String, TypedData>> parts, Map<String, Object> extras, TypedData body) {
List<Pair<String, TypedData>> parts, FileValue file, Body body, Map<String, Object> extras) {
this.method = method;
this.path = path;
this.queries = queries;
@ -49,6 +57,7 @@ public final class RequestInfo {
this.headers = headers;
this.parts = parts;
this.extras = extras;
this.file = file;
this.body = body;
}
@ -72,8 +81,25 @@ public final class RequestInfo {
return extras;
}
@Nullable
public TypedData getBody() {
return body;
if (bodyCache != null) return bodyCache;
if (body == null) return null;
switch (body.value()) {
case FORM: {
bodyCache = new FormTypedBody(getForms());
break;
}
case MULTIPART: {
bodyCache = new MultipartTypedBody(getParts());
break;
}
case FILE: {
bodyCache = file.body();
break;
}
}
return bodyCache;
}
public String getPath() {

View File

@ -1,14 +1,11 @@
package org.mariotaku.simplerestapi;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
import org.apache.commons.lang3.NotImplementedException;
import org.mariotaku.simplerestapi.http.ValueMap;
import org.mariotaku.simplerestapi.http.mime.BaseTypedData;
import org.mariotaku.simplerestapi.http.mime.FormTypedBody;
import org.mariotaku.simplerestapi.http.mime.MultipartTypedBody;
import org.mariotaku.simplerestapi.http.mime.TypedData;
import org.mariotaku.simplerestapi.param.Body;
import org.mariotaku.simplerestapi.param.Extra;
@ -63,25 +60,112 @@ public final class RestMethodInfo {
this.file = file;
}
@Nullable
public TypedData getBody() {
if (bodyCache != null) return bodyCache;
if (body == null) return null;
switch (body.value()) {
case FORM: {
bodyCache = new FormTypedBody(getForms());
break;
}
case MULTIPART: {
bodyCache = new MultipartTypedBody(getParts());
break;
}
case FILE: {
bodyCache = file.body();
static RestMethodInfo get(Method method, Object[] args) {
RestMethod restMethod = null;
String pathFormat = null;
for (Annotation annotation : method.getAnnotations()) {
final Class<?> annotationType = annotation.annotationType();
restMethod = annotationType.getAnnotation(RestMethod.class);
if (restMethod != null) {
try {
pathFormat = (String) annotationType.getMethod("value").invoke(annotation);
} catch (Exception e) {
throw new RuntimeException(e);
}
break;
}
}
return bodyCache;
final Body body = method.getAnnotation(Body.class);
final HashMap<Path, Object> paths = new HashMap<>();
final HashMap<Query, Object> queries = new HashMap<>();
final HashMap<Header, Object> headers = new HashMap<>();
final HashMap<Form, Object> forms = new HashMap<>();
final HashMap<Part, Object> parts = new HashMap<>();
final HashMap<Extra, Object> extras = new HashMap<>();
FileValue file = null;
final Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0, j = annotations.length; i < j; i++) {
final Path path = getAnnotation(annotations[i], Path.class);
if (path != null) {
paths.put(path, args[i]);
}
final Query query = getAnnotation(annotations[i], Query.class);
if (query != null) {
queries.put(query, args[i]);
}
final Header header = getAnnotation(annotations[i], Header.class);
if (header != null) {
headers.put(header, args[i]);
}
final Form form = getAnnotation(annotations[i], Form.class);
if (form != null) {
forms.put(form, args[i]);
}
final Part part = getAnnotation(annotations[i], Part.class);
if (part != null) {
parts.put(part, args[i]);
}
final File paramFile = getAnnotation(annotations[i], File.class);
if (paramFile != null) {
if (file == null) {
file = new FileValue(paramFile, args[i]);
} else {
throw new IllegalArgumentException();
}
}
final Extra extra = getAnnotation(annotations[i], Extra.class);
if (extra != null) {
extras.put(extra, args[i]);
}
}
checkMethod(restMethod, body, forms, parts, file);
return new RestMethodInfo(restMethod, pathFormat, body, paths, queries, headers, forms, parts, file, extras);
}
private static String[] getValueMapKeys(String[] annotationValue, ValueMap valueMap) {
return annotationValue != null && annotationValue.length > 0 ? annotationValue : valueMap.keys();
}
private static void checkMethod(RestMethod restMethod, Body body, HashMap<Form, Object> forms, HashMap<Part, Object> parts, FileValue file) {
if (restMethod == null)
throw new NotImplementedException("Method must has annotation annotated with @RestMethod");
if (!restMethod.hasBody() && body != null) {
throw new IllegalArgumentException(restMethod.value() + " does not allow body");
}
if (body == null) return;
switch (body.value()) {
case FILE: {
if (file == null) {
throw new NullPointerException("@File annotation is required");
}
if (!forms.isEmpty() || !parts.isEmpty()) {
throw new IllegalArgumentException("Only arguments with @File annotation allowed");
}
break;
}
case MULTIPART: {
if (!forms.isEmpty() || file != null) {
throw new IllegalArgumentException("Only arguments with @Part annotation allowed");
}
break;
}
case FORM: {
if (file != null || !parts.isEmpty()) {
throw new IllegalArgumentException("Only arguments with @Form annotation allowed");
}
break;
}
}
}
@SuppressWarnings("unchecked")
private static <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T> annotationClass) {
for (Annotation annotation : annotations) {
if (annotationClass.isAssignableFrom(annotation.annotationType())) {
return (T) annotation;
}
}
return null;
}
@NonNull
@ -220,104 +304,6 @@ public final class RestMethodInfo {
return queriesCache = list;
}
static RestMethodInfo get(Method method, Object[] args) {
RestMethod restMethod = null;
String pathFormat = null;
for (Annotation annotation : method.getAnnotations()) {
final Class<?> annotationType = annotation.annotationType();
restMethod = annotationType.getAnnotation(RestMethod.class);
if (restMethod != null) {
try {
pathFormat = (String) annotationType.getMethod("value").invoke(annotation);
} catch (Exception e) {
throw new RuntimeException(e);
}
break;
}
}
final Body body = method.getAnnotation(Body.class);
final HashMap<Path, Object> paths = new HashMap<>();
final HashMap<Query, Object> queries = new HashMap<>();
final HashMap<Header, Object> headers = new HashMap<>();
final HashMap<Form, Object> forms = new HashMap<>();
final HashMap<Part, Object> parts = new HashMap<>();
final HashMap<Extra, Object> extras = new HashMap<>();
FileValue file = null;
final Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0, j = annotations.length; i < j; i++) {
final Path path = getAnnotation(annotations[i], Path.class);
if (path != null) {
paths.put(path, args[i]);
}
final Query query = getAnnotation(annotations[i], Query.class);
if (query != null) {
queries.put(query, args[i]);
}
final Header header = getAnnotation(annotations[i], Header.class);
if (header != null) {
headers.put(header, args[i]);
}
final Form form = getAnnotation(annotations[i], Form.class);
if (form != null) {
forms.put(form, args[i]);
}
final Part part = getAnnotation(annotations[i], Part.class);
if (part != null) {
parts.put(part, args[i]);
}
final File paramFile = getAnnotation(annotations[i], File.class);
if (paramFile != null) {
if (file == null) {
file = new FileValue(paramFile, args[i]);
} else {
throw new IllegalArgumentException();
}
}
final Extra extra = getAnnotation(annotations[i], Extra.class);
if (extra != null) {
extras.put(extra, args[i]);
}
}
checkMethod(restMethod, body, forms, parts, file);
return new RestMethodInfo(restMethod, pathFormat, body, paths, queries, headers, forms, parts, file, extras);
}
private static String[] getValueMapKeys(String[] annotationValue, ValueMap valueMap) {
return annotationValue != null && annotationValue.length > 0 ? annotationValue : valueMap.keys();
}
private static void checkMethod(RestMethod restMethod, Body body, HashMap<Form, Object> forms, HashMap<Part, Object> parts, FileValue file) {
if (restMethod == null)
throw new NotImplementedException("Method must has annotation annotated with @RestMethod");
if (!restMethod.hasBody() && body != null) {
throw new IllegalArgumentException(restMethod.value() + " does not allow body");
}
if (body == null) return;
switch (body.value()) {
case FILE: {
if (file == null) {
throw new NullPointerException("@File annotation is required");
}
if (!forms.isEmpty() || !parts.isEmpty()) {
throw new IllegalArgumentException("Only arguments with @File annotation allowed");
}
break;
}
case MULTIPART: {
if (!forms.isEmpty() || file != null) {
throw new IllegalArgumentException("Only arguments with @Part annotation allowed");
}
break;
}
case FORM: {
if (file != null || !parts.isEmpty()) {
throw new IllegalArgumentException("Only arguments with @Form annotation allowed");
}
break;
}
}
}
private String findPathReplacement(String key) {
for (Map.Entry<Path, Object> entry : paths.entrySet()) {
final Path path = entry.getKey();
@ -332,23 +318,20 @@ public final class RestMethodInfo {
return null;
}
@SuppressWarnings("unchecked")
private static <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T> annotationClass) {
for (Annotation annotation : annotations) {
if (annotationClass.isAssignableFrom(annotation.annotationType())) {
return (T) annotation;
}
}
return null;
}
public RequestInfo toRequestInfo() {
return new RequestInfo(getMethod().value(), getPath(), getQueries(), getForms(),
getHeaders(), getParts(), getExtras(), getBody());
getHeaders(), getParts(), getFile(), getBody(), getExtras());
}
public RequestInfo toRequestInfo(RequestInfo.Factory factory) {
return factory.create(this);
}
public Body getBody() {
return body;
}
public FileValue getFile() {
return file;
}
}

View File

@ -106,4 +106,17 @@ public final class ContentType {
}
return new ContentType(contentType, parameters);
}
public ContentType charset(Charset charset) {
removeParameter("charset");
return parameter("charset", charset.name());
}
private void removeParameter(String name) {
for (int i = parameters.size() - 1; i >= 0; i++) {
if (name.equals(parameters.get(i).first)) {
parameters.remove(i);
}
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.simplerestapi.http.mime;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.mariotaku.simplerestapi.Utils;
import org.mariotaku.simplerestapi.http.ContentType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Created by mariotaku on 15/5/12.
*/
public class StringTypedData implements TypedData {
private final ContentType contentType;
private final byte[] data;
private ByteArrayInputStream is;
public StringTypedData(String string, Charset charset) {
this.contentType = ContentType.parse("text/plain").charset(charset);
this.data = string.getBytes(charset);
}
@Nullable
@Override
public ContentType contentType() {
return contentType;
}
@Override
public String contentEncoding() {
return null;
}
@Override
public long length() throws IOException {
return data.length;
}
@Override
public void writeTo(@NonNull OutputStream os) throws IOException {
os.write(data);
}
@NonNull
@Override
public InputStream stream() throws IOException {
if (is != null) return is;
return is = new ByteArrayInputStream(data);
}
@Override
public void close() throws IOException {
Utils.closeSilently(is);
}
}

View File

@ -10,6 +10,7 @@ import android.util.Pair;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.internal.Internal;
import org.mariotaku.simplerestapi.FileValue;
import org.mariotaku.simplerestapi.RequestInfo;
import org.mariotaku.simplerestapi.RestAPIFactory;
import org.mariotaku.simplerestapi.RestMethod;
@ -19,6 +20,7 @@ import org.mariotaku.simplerestapi.http.Endpoint;
import org.mariotaku.simplerestapi.http.RestHttpClient;
import org.mariotaku.simplerestapi.http.RestHttpRequest;
import org.mariotaku.simplerestapi.http.RestHttpResponse;
import org.mariotaku.simplerestapi.http.mime.StringTypedData;
import org.mariotaku.simplerestapi.http.mime.TypedData;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.api.twitter.Twitter;
@ -40,6 +42,7 @@ import org.mariotaku.twidere.util.net.OkHttpRestClient;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -170,23 +173,35 @@ public class TwitterAPIUtils implements TwidereConstants {
final List<Pair<String, String>> forms = new ArrayList<>(methodInfo.getForms());
final List<Pair<String, String>> headers = methodInfo.getHeaders();
final List<Pair<String, TypedData>> parts = methodInfo.getParts();
final FileValue file = methodInfo.getFile();
final Map<String, Object> extras = methodInfo.getExtras();
final TypedData body = methodInfo.getBody();
final List<Pair<String, String>> params = method.hasBody() ? forms : queries;
addParameter(params, "include_cards", true);
addParameter(params, "cards_platform", "Android-12");
addParameter(params, "include_entities", true);
addParameter(params, "include_my_retweet", 1);
addParameter(params, "include_rts", 1);
addParameter(params, "include_reply_count", true);
addParameter(params, "include_descendent_reply_count", true);
return new RequestInfo(method.value(), path, queries, forms, headers, parts, extras, body);
if (parts.isEmpty()) {
final List<Pair<String, String>> params = method.hasBody() ? forms : queries;
addParameter(params, "include_cards", true);
addParameter(params, "cards_platform", "Android-12");
addParameter(params, "include_entities", true);
addParameter(params, "include_my_retweet", 1);
addParameter(params, "include_rts", 1);
addParameter(params, "include_reply_count", true);
addParameter(params, "include_descendent_reply_count", true);
} else {
addPart(parts, "include_cards", true);
addPart(parts, "cards_platform", "Android-12");
addPart(parts, "include_entities", true);
addPart(parts, "include_my_retweet", 1);
addPart(parts, "include_rts", 1);
addPart(parts, "include_reply_count", true);
addPart(parts, "include_descendent_reply_count", true);
}
return new RequestInfo(method.value(), path, queries, forms, headers, parts, file,
methodInfo.getBody(), extras);
}
});
factory.setRequestFactory(new RestHttpRequest.Factory() {
@Override
public RestHttpRequest create(@NonNull Endpoint endpoint, @NonNull RequestInfo info, @Nullable Authorization authorization) {
public RestHttpRequest create(@NonNull Endpoint endpoint, @NonNull RequestInfo info,
@Nullable Authorization authorization) {
final String restMethod = info.getMethod();
final String url = Endpoint.constructUrl(endpoint.getUrl(), info);
final ArrayList<Pair<String, String>> headers = new ArrayList<>(info.getHeaders());
@ -213,6 +228,11 @@ public class TwitterAPIUtils implements TwidereConstants {
params.add(Pair.create(name, String.valueOf(value)));
}
private static void addPart(List<Pair<String, TypedData>> params, String name, Object value) {
final TypedData typedData = new StringTypedData(String.valueOf(value), Charset.defaultCharset());
params.add(Pair.create(name, typedData));
}
public static RestHttpClient getDefaultHttpClient(final Context context) {
if (context == null) return null;
final SharedPreferencesWrapper prefs = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);

View File

@ -176,7 +176,8 @@ public class TwidereImageDownloader extends BaseImageDownloader implements Const
queries.add(Pair.create(name, value));
}
}
final RequestInfo info = new RequestInfo(method, uri.getPath(), queries, null, additionalHeaders, null, null, null);
final RequestInfo info = new RequestInfo(method, uri.getPath(), queries, null,
additionalHeaders, null, null, null, null);
additionalHeaders.add(Pair.create("Authorization", auth.getHeader(endpoint, info)));
}
final String requestUri;