add custom api for repo contents (files) and retrofit client

This commit is contained in:
M M Arif
2025-11-08 17:44:35 +05:00
parent 1ccc1077d5
commit ddad33f71c
6 changed files with 1397 additions and 4 deletions

6
.gitignore vendored
View File

@@ -195,7 +195,5 @@ crowdin.yml
!/gradle/wrapper/gradle-wrapper.jar
# End of https://www.gitignore.io/api/android,androidstudio
# Crowdin Config
crowdin.yml
# Others
local.md

1008
APP_STRUCTURE.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
package org.mian.gitnex.api.clients;
import java.util.List;
import org.mian.gitnex.api.models.contents.RepoGetContentsList;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
/**
* @author mmarif
*/
public interface ApiInterface {
@GET("repos/{owner}/{repo}/contents")
Call<List<RepoGetContentsList>> getRepoContents(
@Path("owner") String owner, @Path("repo") String repo, @Query("ref") String ref);
@GET("repos/{owner}/{repo}/contents/{path}")
Call<List<RepoGetContentsList>> getRepoContents(
@Path("owner") String owner,
@Path("repo") String repo,
@Path("path") String path,
@Query("ref") String ref);
}

View File

@@ -0,0 +1,157 @@
package org.mian.gitnex.api.clients;
import android.content.Context;
import java.io.File;
import java.security.SecureRandom;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.mian.gitnex.activities.BaseActivity;
import org.mian.gitnex.helpers.AppDatabaseSettings;
import org.mian.gitnex.helpers.AppUtil;
import org.mian.gitnex.helpers.FilesData;
import org.mian.gitnex.helpers.ssl.MemorizingTrustManager;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
/**
* @author mmarif
*/
public class ApiRetrofitClient {
private static final Map<String, ApiInterface> instances = new ConcurrentHashMap<>();
private static final int CACHE_SIZE_MB = 50;
private static final int MAX_STALE_SECONDS = 60 * 60 * 24 * 30;
public static ApiInterface getInstance(Context context) {
if (!(context instanceof BaseActivity)) return null;
var account = ((BaseActivity) context).getAccount().getAccount();
String url = account.getInstanceUrl();
String token = ((BaseActivity) context).getAccount().getAuthorization();
File cacheFile = new File(context.getCacheDir(), "http-cache");
String key = token.hashCode() + "@" + url;
return instances.computeIfAbsent(key, k -> createApi(context, url, token, cacheFile));
}
private static ApiInterface createApi(
Context context, String url, String token, File cacheFile) {
OkHttpClient client = buildOkHttpClient(context, token, cacheFile);
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit.create(ApiInterface.class);
}
private static OkHttpClient buildOkHttpClient(Context context, String token, File cacheFile) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
MemorizingTrustManager trustManager = new MemorizingTrustManager(context);
sslContext.init(null, new X509TrustManager[] {trustManager}, new SecureRandom());
// HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder =
new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
// .addInterceptor(logging)
.hostnameVerifier(
trustManager.wrapHostnameVerifier(
HttpsURLConnection.getDefaultHostnameVerifier()))
.addInterceptor(userAgentInterceptor(context))
.addInterceptor(authInterceptor(token));
if (cacheFile != null) {
int cacheSize = getCacheSize(context);
File cacheDir = new File(context.getCacheDir(), "http-cache");
if (!cacheDir.exists()) cacheDir.mkdirs();
builder.cache(new Cache(cacheDir, cacheSize))
.addInterceptor(cacheInterceptor(context))
.addNetworkInterceptor(networkInterceptor());
}
return builder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Interceptor userAgentInterceptor(Context ctx) {
return chain -> {
Request req =
chain.request()
.newBuilder()
.header(
"User-Agent",
"GitNex/"
+ AppUtil.getAppVersion(ctx)
+ " (Android "
+ android.os.Build.VERSION.RELEASE
+ ")")
.build();
return chain.proceed(req);
};
}
private static Interceptor authInterceptor(String token) {
return chain ->
chain.proceed(chain.request().newBuilder().header("Authorization", token).build());
}
private static Interceptor cacheInterceptor(Context ctx) {
return chain -> {
Request req = chain.request();
boolean hasNetwork = AppUtil.hasNetworkConnection(ctx);
CacheControl control =
hasNetwork
? CacheControl.FORCE_NETWORK
: new CacheControl.Builder()
.onlyIfCached()
.maxStale(MAX_STALE_SECONDS, TimeUnit.SECONDS)
.build();
return chain.proceed(req.newBuilder().cacheControl(control).build());
};
}
private static Interceptor networkInterceptor() {
return chain -> {
Response resp = chain.proceed(chain.request());
if ("GET".equals(chain.request().method()) && resp.isSuccessful()) {
return resp.newBuilder()
.header(
"Cache-Control",
"public, only-if-cached, max-stale=" + MAX_STALE_SECONDS)
.removeHeader("Pragma")
.build();
}
return resp;
};
}
private static int getCacheSize(Context ctx) {
try {
return FilesData.returnOnlyNumberFileSize(
AppDatabaseSettings.getSettingsValue(
ctx, AppDatabaseSettings.APP_DATA_CACHE_SIZE_KEY))
* 1024
* 1024;
} catch (Exception e) {
return CACHE_SIZE_MB * 1024 * 1024;
}
}
}

View File

@@ -0,0 +1,30 @@
package org.mian.gitnex.api.models.contents;
import com.google.gson.annotations.SerializedName;
/**
* @author mmarif
*/
public class Links {
@SerializedName("git")
private String git;
@SerializedName("self")
private String self;
@SerializedName("html")
private String html;
public String getGit() {
return git;
}
public String getSelf() {
return self;
}
public String getHtml() {
return html;
}
}

View File

@@ -0,0 +1,175 @@
package org.mian.gitnex.api.models.contents;
import com.google.gson.annotations.SerializedName;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* @author mmarif
*/
public class RepoGetContentsList {
@SerializedName("last_commit_when")
private String lastCommitWhen;
@SerializedName("last_committer_date")
private Date lastCommitterDate;
@SerializedName("last_author_date")
private Date lastAuthorDate;
@SerializedName("submodule_git_url")
private String submoduleGitUrl;
@SerializedName("_links")
private Links links;
@SerializedName("last_commit_sha")
private String lastCommitSha;
@SerializedName("type")
private String type;
@SerializedName("encoding")
private String encoding;
@SerializedName("sha")
private String sha;
@SerializedName("content")
private String content;
@SerializedName("url")
private String url;
@SerializedName("target")
private String target;
@SerializedName("path")
private String path;
@SerializedName("size")
private int size;
@SerializedName("html_url")
private String htmlUrl;
@SerializedName("name")
private String name;
@SerializedName("download_url")
private String downloadUrl;
@SerializedName("git_url")
private String gitUrl;
public String getLastCommitWhen() {
return lastCommitWhen;
}
public Date getLastCommiterDate() {
return lastCommitterDate;
}
public Date getLastAuthorDate() {
return lastAuthorDate;
}
public String getSubmoduleGitUrl() {
return submoduleGitUrl;
}
public Links getLinks() {
return links;
}
public String getLastCommitSha() {
return lastCommitSha;
}
public String getType() {
return type;
}
public String getEncoding() {
return encoding;
}
public String getSha() {
return sha;
}
public String getContent() {
return content;
}
public String getUrl() {
return url;
}
public String getTarget() {
return target;
}
public String getPath() {
return path;
}
public int getSize() {
return size;
}
public String getHtmlUrl() {
return htmlUrl;
}
public String getName() {
return name;
}
public String getDownloadUrl() {
return downloadUrl;
}
public String getGitUrl() {
return gitUrl;
}
public Date getCompatibleCommitDate() {
// First try Gitea field
if (lastCommitterDate != null) {
return lastCommitterDate;
}
// Then try Forgejo field
if (lastCommitWhen != null) {
return parseForgejoDateString(lastCommitWhen);
}
return null;
}
private Date parseForgejoDateString(String dateString) {
if (dateString == null) return null;
String[] dateFormats = {
"yyyy-MM-dd'T'HH:mm:ssXXX",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
};
for (String format : dateFormats) {
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.getDefault());
dateFormat.setLenient(false);
return dateFormat.parse(dateString);
} catch (ParseException e) {
// Try next format
}
}
return null;
}
}