2019-06-03 23:25:05 +02:00
|
|
|
package org.mian.gitnex.clients;
|
|
|
|
|
2019-11-13 16:52:49 +01:00
|
|
|
import android.content.Context;
|
2020-03-31 16:41:50 +02:00
|
|
|
import android.util.Log;
|
2022-04-18 09:10:54 +02:00
|
|
|
import com.google.gson.GsonBuilder;
|
2022-09-21 07:43:00 +02:00
|
|
|
import java.io.File;
|
|
|
|
import java.lang.annotation.Annotation;
|
|
|
|
import java.lang.reflect.Type;
|
|
|
|
import java.security.SecureRandom;
|
|
|
|
import java.text.DateFormat;
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.Locale;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Objects;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
|
|
import javax.net.ssl.SSLContext;
|
|
|
|
import javax.net.ssl.X509TrustManager;
|
|
|
|
import okhttp3.Cache;
|
|
|
|
import okhttp3.OkHttpClient;
|
|
|
|
import okhttp3.Request;
|
2022-08-26 19:00:08 +02:00
|
|
|
import org.gitnex.tea4j.v2.apis.AdminApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.IssueApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.MiscellaneousApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.NotificationApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.OrganizationApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.PackageApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.RepositoryApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.SettingsApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.UserApi;
|
2022-04-18 09:10:54 +02:00
|
|
|
import org.gitnex.tea4j.v2.apis.custom.CustomApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.custom.OTPApi;
|
|
|
|
import org.gitnex.tea4j.v2.apis.custom.WebApi;
|
|
|
|
import org.gitnex.tea4j.v2.auth.ApiKeyAuth;
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
Don't use TinyDB as cache (#1034)
Do not use TinyDB as a cache or a way to send data between activities.
### How is this working
Instead of saving everything into the TinyDB, I created three `Context`s (a `RepositoryContext`, an `IssueContext` and an `AccountContext`). All are used to store things like API or database values/models and additional data, e.g. the `RepositoryContext` also contains information about the current filter state of a repository (issues, pull requests, releases/tags and milestones). These are sent using `Intent`s and `Bundle`s between activities and fragments. Changing a field (e.g. filter state) in any fragment changes it also for the whole repository (or at least it should do so).
Due to the size of the changes (after https://codeberg.org/gitnex/GitNex/commit/c9172f85efafd9f25739fdd8385e1904b711ea41, Git says `154 files changed, 3318 insertions(+), 3835 deletions(-)`) **I highly recommend you to create a beta/pre release before releasing a stable version**.
Additional changes:
* after logging out, the account remains in the account list (with a note) and you can log in again (you can't switch to this account)
* repositories and organizations are clickable on user profiles
* deleted two unused classes
Once finished, hopefully
* closes #354
* replaces #897
* fixes #947
* closes #1001
* closes #1015
* marks #876 and #578 as `Wontfix` since they are not necessary at this point
* and all the other TinyDB issues
Co-authored-by: qwerty287 <ndev@web.de>
Co-authored-by: M M Arif <mmarif@noreply.codeberg.org>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1034
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: qwerty287 <qwerty287@noreply.codeberg.org>
Co-committed-by: qwerty287 <qwerty287@noreply.codeberg.org>
2022-03-13 03:59:13 +01:00
|
|
|
import org.mian.gitnex.activities.BaseActivity;
|
2024-03-18 06:12:24 +01:00
|
|
|
import org.mian.gitnex.helpers.AppDatabaseSettings;
|
2020-12-31 15:38:18 +01:00
|
|
|
import org.mian.gitnex.helpers.AppUtil;
|
2020-05-09 16:50:45 +02:00
|
|
|
import org.mian.gitnex.helpers.FilesData;
|
2020-04-30 12:42:22 +02:00
|
|
|
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
|
2022-04-18 09:10:54 +02:00
|
|
|
import retrofit2.Converter;
|
2019-06-03 23:25:05 +02:00
|
|
|
import retrofit2.Retrofit;
|
|
|
|
import retrofit2.converter.gson.GsonConverterFactory;
|
|
|
|
import retrofit2.converter.scalars.ScalarsConverterFactory;
|
|
|
|
|
|
|
|
/**
|
2022-04-22 17:18:38 +02:00
|
|
|
* @author M M Arif
|
2019-06-03 23:25:05 +02:00
|
|
|
*/
|
|
|
|
public class RetrofitClient {
|
|
|
|
|
2021-01-01 18:30:30 +01:00
|
|
|
private static final Map<String, ApiInterface> apiInterfaces = new ConcurrentHashMap<>();
|
2022-04-18 09:10:54 +02:00
|
|
|
private static final Map<String, WebApi> webInterfaces = new ConcurrentHashMap<>();
|
2020-03-31 21:14:49 +02:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
private static Retrofit createRetrofit(
|
|
|
|
Context context,
|
|
|
|
String instanceUrl,
|
|
|
|
boolean cacheEnabled,
|
|
|
|
String token,
|
|
|
|
File cacheFile) {
|
2020-03-31 21:14:49 +02:00
|
|
|
|
2022-08-15 16:26:02 +02:00
|
|
|
// HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
|
|
|
|
// logging.setLevel(HttpLoggingInterceptor.Level.BODY);
|
2020-11-17 19:56:16 +01:00
|
|
|
|
2020-04-01 17:28:14 +02:00
|
|
|
try {
|
|
|
|
|
2020-03-31 21:14:49 +02:00
|
|
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
|
|
|
2020-11-02 16:17:00 +01:00
|
|
|
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(context);
|
2022-09-21 07:43:00 +02:00
|
|
|
sslContext.init(
|
|
|
|
null, new X509TrustManager[] {memorizingTrustManager}, new SecureRandom());
|
2020-03-31 21:14:49 +02:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
ApiKeyAuth auth = new ApiKeyAuth("header", "Authorization");
|
|
|
|
auth.setApiKey(token);
|
2022-09-21 07:43:00 +02:00
|
|
|
OkHttpClient.Builder okHttpClient =
|
|
|
|
new OkHttpClient.Builder()
|
|
|
|
// .addInterceptor(logging)
|
|
|
|
.addInterceptor(auth)
|
|
|
|
.sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager)
|
|
|
|
.hostnameVerifier(
|
|
|
|
memorizingTrustManager.wrapHostnameVerifier(
|
|
|
|
HttpsURLConnection.getDefaultHostnameVerifier()));
|
|
|
|
|
|
|
|
if (cacheEnabled && cacheFile != null) {
|
|
|
|
|
|
|
|
int cacheSize =
|
|
|
|
FilesData.returnOnlyNumberFileSize(
|
2024-03-18 06:12:24 +01:00
|
|
|
AppDatabaseSettings.getSettingsValue(
|
|
|
|
context,
|
|
|
|
AppDatabaseSettings.APP_DATA_CACHE_SIZE_KEY))
|
2022-09-21 07:43:00 +02:00
|
|
|
* 1024
|
|
|
|
* 1024;
|
2022-04-30 18:55:20 +02:00
|
|
|
Cache cache = new Cache(cacheFile, cacheSize);
|
2021-03-21 16:56:54 +01:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
okHttpClient
|
|
|
|
.cache(cache)
|
|
|
|
.addInterceptor(
|
|
|
|
chain -> {
|
|
|
|
Request request = chain.request();
|
|
|
|
|
|
|
|
request =
|
|
|
|
AppUtil.hasNetworkConnection(context)
|
|
|
|
? request.newBuilder()
|
|
|
|
.header(
|
|
|
|
"Cache-Control",
|
|
|
|
"public, max-age=" + 60)
|
|
|
|
.build()
|
|
|
|
: request.newBuilder()
|
|
|
|
.header(
|
|
|
|
"Cache-Control",
|
|
|
|
"public, only-if-cached, max-stale="
|
|
|
|
+ 60 * 60 * 24 * 30)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
return chain.proceed(request);
|
|
|
|
});
|
2021-03-21 16:56:54 +01:00
|
|
|
}
|
2020-11-02 16:17:00 +01:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
return new Retrofit.Builder()
|
|
|
|
.baseUrl(instanceUrl)
|
|
|
|
.client(okHttpClient.build())
|
|
|
|
.addConverterFactory(ScalarsConverterFactory.create())
|
|
|
|
.addConverterFactory(
|
|
|
|
GsonConverterFactory.create(
|
|
|
|
new GsonBuilder()
|
|
|
|
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
|
|
|
.create()))
|
|
|
|
.addConverterFactory(DateQueryConverterFactory.create())
|
|
|
|
.build();
|
2020-11-02 16:17:00 +01:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
} catch (Exception e) {
|
2020-03-31 21:14:49 +02:00
|
|
|
|
2020-12-31 15:38:18 +01:00
|
|
|
Log.e("onFailureRetrofit", e.toString());
|
2020-03-31 21:14:49 +02:00
|
|
|
}
|
2020-04-01 17:28:14 +02:00
|
|
|
|
2020-11-02 16:17:00 +01:00
|
|
|
return null;
|
2020-03-31 21:14:49 +02:00
|
|
|
}
|
|
|
|
|
2021-01-01 18:30:30 +01:00
|
|
|
public static ApiInterface getApiInterface(Context context) {
|
2022-09-21 07:43:00 +02:00
|
|
|
return getApiInterface(
|
|
|
|
context,
|
|
|
|
((BaseActivity) context).getAccount().getAccount().getInstanceUrl(),
|
|
|
|
((BaseActivity) context).getAccount().getAuthorization(),
|
|
|
|
((BaseActivity) context).getAccount().getCacheDir(context));
|
2020-03-31 21:14:49 +02:00
|
|
|
}
|
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
public static WebApi getWebInterface(Context context) {
|
2020-11-02 16:17:00 +01:00
|
|
|
|
Don't use TinyDB as cache (#1034)
Do not use TinyDB as a cache or a way to send data between activities.
### How is this working
Instead of saving everything into the TinyDB, I created three `Context`s (a `RepositoryContext`, an `IssueContext` and an `AccountContext`). All are used to store things like API or database values/models and additional data, e.g. the `RepositoryContext` also contains information about the current filter state of a repository (issues, pull requests, releases/tags and milestones). These are sent using `Intent`s and `Bundle`s between activities and fragments. Changing a field (e.g. filter state) in any fragment changes it also for the whole repository (or at least it should do so).
Due to the size of the changes (after https://codeberg.org/gitnex/GitNex/commit/c9172f85efafd9f25739fdd8385e1904b711ea41, Git says `154 files changed, 3318 insertions(+), 3835 deletions(-)`) **I highly recommend you to create a beta/pre release before releasing a stable version**.
Additional changes:
* after logging out, the account remains in the account list (with a note) and you can log in again (you can't switch to this account)
* repositories and organizations are clickable on user profiles
* deleted two unused classes
Once finished, hopefully
* closes #354
* replaces #897
* fixes #947
* closes #1001
* closes #1015
* marks #876 and #578 as `Wontfix` since they are not necessary at this point
* and all the other TinyDB issues
Co-authored-by: qwerty287 <ndev@web.de>
Co-authored-by: M M Arif <mmarif@noreply.codeberg.org>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://codeberg.org/gitnex/GitNex/pulls/1034
Reviewed-by: 6543 <6543@noreply.codeberg.org>
Co-authored-by: qwerty287 <qwerty287@noreply.codeberg.org>
Co-committed-by: qwerty287 <qwerty287@noreply.codeberg.org>
2022-03-13 03:59:13 +01:00
|
|
|
String instanceUrl = ((BaseActivity) context).getAccount().getAccount().getInstanceUrl();
|
2020-11-02 16:17:00 +01:00
|
|
|
instanceUrl = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/"));
|
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
return getWebInterface(
|
|
|
|
context,
|
|
|
|
instanceUrl,
|
|
|
|
((BaseActivity) context).getAccount().getWebAuthorization(),
|
|
|
|
((BaseActivity) context).getAccount().getCacheDir(context));
|
2020-03-31 21:14:49 +02:00
|
|
|
}
|
2019-06-03 23:25:05 +02:00
|
|
|
|
2024-02-11 14:20:23 +01:00
|
|
|
public static WebApi getWebInterface(Context context, String url) {
|
|
|
|
|
|
|
|
return getWebInterface(
|
|
|
|
context,
|
|
|
|
url,
|
|
|
|
((BaseActivity) context).getAccount().getAuthorization(),
|
|
|
|
((BaseActivity) context).getAccount().getCacheDir(context));
|
|
|
|
}
|
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
public static ApiInterface getApiInterface(
|
|
|
|
Context context, String url, String token, File cacheFile) {
|
2021-01-01 18:30:30 +01:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
String key = token.hashCode() + "@" + url;
|
2022-09-21 07:43:00 +02:00
|
|
|
if (!apiInterfaces.containsKey(key)) {
|
|
|
|
synchronized (RetrofitClient.class) {
|
|
|
|
if (!apiInterfaces.containsKey(key)) {
|
|
|
|
|
|
|
|
ApiInterface apiInterface =
|
|
|
|
Objects.requireNonNull(
|
|
|
|
createRetrofit(context, url, true, token, cacheFile))
|
|
|
|
.create(ApiInterface.class);
|
2022-04-18 09:10:54 +02:00
|
|
|
apiInterfaces.put(key, apiInterface);
|
2020-04-30 12:42:22 +02:00
|
|
|
|
2021-03-03 15:10:23 +01:00
|
|
|
return apiInterface;
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 18:30:30 +01:00
|
|
|
}
|
2020-11-02 16:17:00 +01:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
return apiInterfaces.get(key);
|
2020-04-30 12:42:22 +02:00
|
|
|
}
|
2020-04-02 18:00:03 +02:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
public static WebApi getWebInterface(
|
|
|
|
Context context, String url, String token, File cacheFile) {
|
2020-11-02 16:17:00 +01:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
String key = token.hashCode() + "@" + url;
|
2022-09-21 07:43:00 +02:00
|
|
|
if (!webInterfaces.containsKey(key)) {
|
|
|
|
synchronized (RetrofitClient.class) {
|
|
|
|
if (!webInterfaces.containsKey(key)) {
|
|
|
|
|
|
|
|
WebApi webInterface =
|
|
|
|
Objects.requireNonNull(
|
|
|
|
createRetrofit(context, url, false, token, cacheFile))
|
|
|
|
.create(WebApi.class);
|
2022-04-18 09:10:54 +02:00
|
|
|
webInterfaces.put(key, webInterface);
|
2021-01-01 18:30:30 +01:00
|
|
|
|
2021-03-03 15:10:23 +01:00
|
|
|
return webInterface;
|
|
|
|
}
|
|
|
|
}
|
2021-01-01 18:30:30 +01:00
|
|
|
}
|
2020-11-02 16:17:00 +01:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
return webInterfaces.get(key);
|
2020-11-02 16:17:00 +01:00
|
|
|
}
|
2022-04-18 09:10:54 +02:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
public interface ApiInterface
|
|
|
|
extends AdminApi,
|
|
|
|
OrganizationApi,
|
|
|
|
IssueApi,
|
|
|
|
RepositoryApi,
|
|
|
|
MiscellaneousApi,
|
|
|
|
NotificationApi,
|
|
|
|
UserApi,
|
|
|
|
SettingsApi,
|
|
|
|
OTPApi,
|
|
|
|
CustomApi,
|
|
|
|
PackageApi {}
|
2022-04-18 09:10:54 +02:00
|
|
|
|
|
|
|
private static class DateQueryConverterFactory extends Converter.Factory {
|
2022-08-15 16:26:02 +02:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
public static DateQueryConverterFactory create() {
|
|
|
|
return new DateQueryConverterFactory();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-09-21 07:43:00 +02:00
|
|
|
public Converter<?, String> stringConverter(
|
|
|
|
@NotNull Type type, @NotNull Annotation[] annotations, @NotNull Retrofit retrofit) {
|
|
|
|
if (type == Date.class) {
|
2022-04-18 09:10:54 +02:00
|
|
|
return DateQueryConverter.INSTANCE;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final class DateQueryConverter implements Converter<Date, String> {
|
2022-08-15 16:26:02 +02:00
|
|
|
|
2022-04-18 09:10:54 +02:00
|
|
|
static final DateQueryConverter INSTANCE = new DateQueryConverter();
|
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
private static final ThreadLocal<DateFormat> DF =
|
|
|
|
new ThreadLocal<>() {
|
2022-04-18 09:10:54 +02:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
@Override
|
|
|
|
public DateFormat initialValue() {
|
2022-04-18 09:10:54 +02:00
|
|
|
|
2022-09-21 07:43:00 +02:00
|
|
|
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
|
|
|
}
|
|
|
|
};
|
2022-04-18 09:10:54 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public String convert(@NotNull Date date) {
|
|
|
|
return Objects.requireNonNull(DF.get()).format(date);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-03 23:25:05 +02:00
|
|
|
}
|