mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2026-04-18 16:46:58 -04:00
Major changes
- Added a textview for no content/network error messages - Reimplemented Modrinth modpack installer with parallel installation in mind - Implemented modloader version ID generation - Implemented profile icons TODO: - Actually figure out how to install the mod loader - Proper IOException handling in the modpack installer - Proper IOException handling when loading detailed mod info - CurseForge
This commit is contained in:
@@ -79,6 +79,7 @@ import java.util.Map;
|
||||
|
||||
@SuppressWarnings("IOStreamConstructor")
|
||||
public final class Tools {
|
||||
public static final float BYTE_TO_MB = 1024 * 1024;
|
||||
public static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||
public static String APP_NAME = "null";
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.kdt.pojavlaunch.fragments;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@@ -37,6 +39,8 @@ public class SearchModFragment extends Fragment {
|
||||
private ModItemAdapter mModItemAdapter;
|
||||
private ProgressBar mSearchProgressBar;
|
||||
private Future<?> mSearchFuture;
|
||||
private TextView mStatusTextView;
|
||||
private ColorStateList mDefaultTextColor;
|
||||
|
||||
private ModpackApi modpackApi;
|
||||
|
||||
@@ -59,6 +63,9 @@ public class SearchModFragment extends Fragment {
|
||||
mSelectedVersion = view.findViewById(R.id.search_mod_selected_mc_version_textview);
|
||||
mSelectVersionButton = view.findViewById(R.id.search_mod_mc_version_button);
|
||||
mRecyclerview = view.findViewById(R.id.search_mod_list);
|
||||
mStatusTextView = view.findViewById(R.id.search_mod_status_text);
|
||||
|
||||
mDefaultTextColor = mStatusTextView.getTextColors();
|
||||
|
||||
mRecyclerview.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
mRecyclerview.setAdapter(mModItemAdapter);
|
||||
@@ -81,6 +88,7 @@ public class SearchModFragment extends Fragment {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
class SearchModRunnable implements Runnable{
|
||||
private final Object mFutureLock = new Object();
|
||||
private final SearchFilters mRunnableFilters;
|
||||
@@ -109,9 +117,22 @@ public class SearchModFragment extends Fragment {
|
||||
ModItem[] items = modpackApi.searchMod(mRunnableFilters);
|
||||
Log.d(SearchModFragment.class.toString(), Arrays.toString(items));
|
||||
Tools.runOnUiThread(() -> {
|
||||
ModItem[] localItems = items;
|
||||
if(mMyFuture.isCancelled()) return;
|
||||
mSearchProgressBar.setVisibility(View.GONE);
|
||||
mModItemAdapter.setModItems(items, mSelectedVersion.getText().toString());
|
||||
if(localItems == null) {
|
||||
mStatusTextView.setVisibility(View.VISIBLE);
|
||||
mStatusTextView.setTextColor(Color.RED);
|
||||
mStatusTextView.setText(R.string.search_modpack_error);
|
||||
}else if(localItems.length == 0) {
|
||||
mStatusTextView.setVisibility(View.VISIBLE);
|
||||
mStatusTextView.setTextColor(mDefaultTextColor);
|
||||
mStatusTextView.setText(R.string.search_modpack_no_result);
|
||||
localItems = null;
|
||||
}else{
|
||||
mStatusTextView.setVisibility(View.GONE);
|
||||
}
|
||||
mModItemAdapter.setModItems(localItems, mSelectedVersion.getText().toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -9,7 +10,6 @@ import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -28,7 +28,7 @@ import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ModItemAdapter extends RecyclerView.Adapter<ModItemAdapter.ViewHolder> {
|
||||
|
||||
private static final ModItem[] MOD_ITEMS_EMPTY = new ModItem[0];
|
||||
private final ModIconCache mIconCache = new ModIconCache();
|
||||
private ModItem[] mModItems;
|
||||
private final ModpackApi mModpackApi;
|
||||
@@ -44,7 +44,7 @@ public class ModItemAdapter extends RecyclerView.Adapter<ModItemAdapter.ViewHold
|
||||
private View mExtendedLayout;
|
||||
private Spinner mExtendedSpinner;
|
||||
private Button mExtendedButton;
|
||||
private ImageView mIconView;
|
||||
private final ImageView mIconView;
|
||||
private Bitmap mThumbnailBitmap;
|
||||
private ImageReceiver mImageReceiver;
|
||||
public ViewHolder(View view) {
|
||||
@@ -102,7 +102,7 @@ public class ModItemAdapter extends RecyclerView.Adapter<ModItemAdapter.ViewHold
|
||||
mThumbnailBitmap = bm;
|
||||
mIconView.setImageBitmap(bm);
|
||||
};
|
||||
mIconCache.getImage(mImageReceiver, mModItem.apiSource+"_"+mModItem.id, mModItem.imageUrl);
|
||||
mIconCache.getImage(mImageReceiver, mModItem.getIconCacheTag(), mModItem.imageUrl);
|
||||
mTitle.setText(item.title);
|
||||
mDescription.setText(item.description);
|
||||
|
||||
@@ -132,8 +132,11 @@ public class ModItemAdapter extends RecyclerView.Adapter<ModItemAdapter.ViewHold
|
||||
mModItems = new ModItem[]{};
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setModItems(ModItem[] items, String targetMcVersion){
|
||||
mModItems = items;
|
||||
// TODO: Use targetMcVersion to affect default selected modpack version
|
||||
if(items != null) mModItems = items;
|
||||
else mModItems = MOD_ITEMS_EMPTY;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,16 @@ package net.kdt.pojavlaunch.modloaders.modpacks.api;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ApiHandler {
|
||||
public final String baseUrl;
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.api;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ModDownloader {
|
||||
private static final ThreadLocal<byte[]> sThreadLocalBuffer = new ThreadLocal<>();
|
||||
private final ThreadPoolExecutor mDownloadPool = new ThreadPoolExecutor(4,4,100, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>());
|
||||
private final AtomicBoolean mTerminator = new AtomicBoolean(false);
|
||||
private final AtomicLong mDownloadSize = new AtomicLong(0);
|
||||
private final Object mExceptionSyncPoint = new Object();
|
||||
private final File mDestinationDirectory;
|
||||
private IOException mFirstIOException;
|
||||
private long mTotalSize;
|
||||
|
||||
public ModDownloader(File destinationDirectory) {
|
||||
this.mDestinationDirectory = destinationDirectory;
|
||||
}
|
||||
|
||||
public void submitDownload(int fileSize, String relativePath, String... url) {
|
||||
mTotalSize += fileSize;
|
||||
mDownloadPool.execute(new DownloadTask(url, new File(mDestinationDirectory, relativePath)));
|
||||
}
|
||||
|
||||
public void awaitFinish(Tools.DownloaderFeedback feedback) throws IOException{
|
||||
try {
|
||||
mDownloadPool.shutdown();
|
||||
while(!mDownloadPool.awaitTermination(20, TimeUnit.MILLISECONDS) && !mTerminator.get()) {
|
||||
feedback.updateProgress((int) mDownloadSize.get(), (int) mTotalSize);
|
||||
}
|
||||
|
||||
if(mTerminator.get()) {
|
||||
synchronized (mExceptionSyncPoint) {
|
||||
if(mFirstIOException == null) mExceptionSyncPoint.wait();
|
||||
throw mFirstIOException;
|
||||
}
|
||||
}
|
||||
}catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getThreadLocalBuffer() {
|
||||
byte[] buffer = sThreadLocalBuffer.get();
|
||||
if(buffer != null) return buffer;
|
||||
buffer = new byte[8192];
|
||||
sThreadLocalBuffer.set(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
class DownloadTask implements Runnable, Tools.DownloaderFeedback {
|
||||
private final String[] mDownloadUrls;
|
||||
private final File mDestination;
|
||||
private int last = 0;
|
||||
|
||||
public DownloadTask(String[] downloadurls,
|
||||
File downloadDestination) {
|
||||
this.mDownloadUrls = downloadurls;
|
||||
this.mDestination = downloadDestination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
IOException exception = null;
|
||||
for(String sourceUrl : mDownloadUrls) {
|
||||
try {
|
||||
exception = tryDownload(sourceUrl);
|
||||
if(exception == null) return;
|
||||
}catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(exception != null) {
|
||||
synchronized (mExceptionSyncPoint) {
|
||||
if(mFirstIOException == null) {
|
||||
mFirstIOException = exception;
|
||||
mExceptionSyncPoint.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IOException tryDownload(String sourceUrl) throws InterruptedException {
|
||||
IOException exception = null;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
try {
|
||||
DownloadUtils.downloadFileMonitored(sourceUrl, mDestination, getThreadLocalBuffer(), this);
|
||||
return null;
|
||||
} catch (InterruptedIOException e) {
|
||||
throw new InterruptedException();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
exception = e;
|
||||
}
|
||||
mDownloadSize.addAndGet(-last);
|
||||
last = 0;
|
||||
}
|
||||
return exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
mDownloadSize.addAndGet(curr - last);
|
||||
last = curr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.api;
|
||||
|
||||
public class ModLoaderInfo {
|
||||
public static final int MOD_LOADER_FORGE = 0;
|
||||
public static final int MOD_LOADER_FABRIC = 1;
|
||||
public static final int MOD_LOADER_QUILT = 2;
|
||||
public final int modLoaderType;
|
||||
public final String modLoaderVersion;
|
||||
public final String minecraftVersion;
|
||||
|
||||
public ModLoaderInfo(int modLoaderType, String modLoaderVersion, String minecraftVersion) {
|
||||
this.modLoaderType = modLoaderType;
|
||||
this.modLoaderVersion = modLoaderVersion;
|
||||
this.minecraftVersion = minecraftVersion;
|
||||
}
|
||||
|
||||
public String getVersionId() {
|
||||
switch (modLoaderType) {
|
||||
case MOD_LOADER_FORGE:
|
||||
return minecraftVersion+"-forge-"+modLoaderType;
|
||||
case MOD_LOADER_FABRIC:
|
||||
return "fabric-loader-"+modLoaderVersion+"-"+minecraftVersion;
|
||||
case MOD_LOADER_QUILT:
|
||||
// TODO
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,6 @@ import net.kdt.pojavlaunch.PojavApplication;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
|
||||
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.api;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.kdt.mcgui.ProgressLayout;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.ModIconCache;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModDetail;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModItem;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.ModrinthIndex;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.Constants;
|
||||
import net.kdt.pojavlaunch.modloaders.modpacks.models.SearchFilters;
|
||||
import net.kdt.pojavlaunch.progresskeeper.DownloaderProgressWrapper;
|
||||
import net.kdt.pojavlaunch.utils.DownloadUtils;
|
||||
import net.kdt.pojavlaunch.utils.FileUtils;
|
||||
import net.kdt.pojavlaunch.utils.ZipUtils;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
|
||||
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
|
||||
|
||||
@@ -20,9 +22,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class ModrinthApi implements ModpackApi {
|
||||
private ApiHandler mApiHandler;
|
||||
public class ModrinthApi implements ModpackApi{
|
||||
private final ApiHandler mApiHandler;
|
||||
public ModrinthApi(){
|
||||
mApiHandler = new ApiHandler("https://api.modrinth.com/v2");
|
||||
}
|
||||
@@ -68,7 +72,7 @@ public class ModrinthApi implements ModpackApi {
|
||||
|
||||
JsonArray response = mApiHandler.get(String.format("project/%s/version", item.id), JsonArray.class);
|
||||
if(response == null) return null;
|
||||
System.out.println(response.toString());
|
||||
System.out.println(response);
|
||||
String[] names = new String[response.size()];
|
||||
String[] mcNames = new String[response.size()];
|
||||
String[] urls = new String[response.size()];
|
||||
@@ -87,70 +91,80 @@ public class ModrinthApi implements ModpackApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installMod(ModDetail modDetail, int selectedVersion){
|
||||
public void installMod(ModDetail modDetail, int selectedVersion) {
|
||||
//TODO considering only modpacks for now
|
||||
String versionUrl = modDetail.versionUrls[selectedVersion];
|
||||
String modpackName = modDetail.title.toLowerCase(Locale.ROOT).trim().replace(" ", "_" );
|
||||
|
||||
// Build a new minecraft instance, folder first
|
||||
File instanceFolder = new File(Tools.DIR_CACHE, modpackName);
|
||||
instanceFolder.mkdirs();
|
||||
|
||||
// Get the mrpack
|
||||
File modpackFile = new File(Tools.DIR_CACHE, modpackName + ".mrpack");
|
||||
ModLoaderInfo modLoaderInfo;
|
||||
try {
|
||||
DownloadUtils.downloadFile(versionUrl, modpackFile);
|
||||
|
||||
FileUtils.uncompressZip(modpackFile, instanceFolder);
|
||||
|
||||
// Get the index
|
||||
ModrinthIndex index = Tools.GLOBAL_GSON.fromJson(Tools.read(instanceFolder.getAbsolutePath() + "/modrinth.index.json"), ModrinthIndex.class);
|
||||
System.out.println(index);
|
||||
// Download mods
|
||||
for (ModrinthIndex.ModrinthIndexFile file : index.files){
|
||||
File destFile = new File(instanceFolder, file.path);
|
||||
destFile.getParentFile().mkdirs();
|
||||
DownloadUtils.downloadFile(file.downloads[0], destFile);
|
||||
}
|
||||
|
||||
// Apply the overrides
|
||||
for(String overrideName : new String[]{"overrides", "client-overrides"}) {
|
||||
File overrideFolder = new File(instanceFolder, overrideName);
|
||||
if(!overrideFolder.exists() || !overrideFolder.isDirectory()){
|
||||
continue;
|
||||
}
|
||||
for(File file : overrideFolder.listFiles()){
|
||||
// TODO what if overrides + client-overrides have collisions ?
|
||||
org.apache.commons.io.FileUtils.moveToDirectory(file, instanceFolder, true);
|
||||
}
|
||||
overrideFolder.delete();
|
||||
}
|
||||
// Remove server override as it is pointless
|
||||
org.apache.commons.io.FileUtils.deleteDirectory(new File(instanceFolder, "server-overrides"));
|
||||
|
||||
// Move the instance folder
|
||||
org.apache.commons.io.FileUtils.moveDirectoryToDirectory(instanceFolder, new File(Tools.DIR_GAME_HOME, "custom_instances"), true);
|
||||
|
||||
byte[] downloadBuffer = new byte[8192];
|
||||
DownloadUtils.downloadFileMonitored(versionUrl, modpackFile, downloadBuffer,
|
||||
new DownloaderProgressWrapper(R.string.modpack_download_downloading_metadata,
|
||||
ProgressLayout.INSTALL_MODPACK));
|
||||
ModrinthIndex modrinthIndex = installMrpack(modpackFile, new File(Tools.DIR_GAME_HOME, "custom_instances/"+modpackName));
|
||||
modLoaderInfo = createInfo(modrinthIndex);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
modpackFile.delete();
|
||||
try {
|
||||
org.apache.commons.io.FileUtils.deleteDirectory(instanceFolder);
|
||||
} catch (IOException e) {
|
||||
Log.e(ModrinthApi.class.toString(), "Failed to cleanup cache instance folder, if any");
|
||||
}
|
||||
ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK);
|
||||
}
|
||||
if(modLoaderInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the instance
|
||||
MinecraftProfile profile = new MinecraftProfile();
|
||||
profile.gameDir = "./custom_instances/" + modpackName;
|
||||
profile.name = modpackName;
|
||||
//FIXME add the proper version !
|
||||
profile.lastVersionId = "1.7.10";
|
||||
profile.name = modDetail.title;
|
||||
profile.lastVersionId = modLoaderInfo.getVersionId();
|
||||
profile.icon = ModIconCache.getBase64Image(modDetail.getIconCacheTag());
|
||||
|
||||
|
||||
LauncherProfiles.mainProfileJson.profiles.put(modpackName, profile);
|
||||
LauncherProfiles.update();
|
||||
}
|
||||
|
||||
private static ModLoaderInfo createInfo(ModrinthIndex modrinthIndex) {
|
||||
if(modrinthIndex == null) return null;
|
||||
Map<String, String> dependencies = modrinthIndex.dependencies;
|
||||
String mcVersion = dependencies.get("minecraft");
|
||||
if(mcVersion == null) return null;
|
||||
String modLoaderVersion;
|
||||
if((modLoaderVersion = dependencies.get("forge")) != null) {
|
||||
return new ModLoaderInfo(ModLoaderInfo.MOD_LOADER_FORGE, modLoaderVersion, mcVersion);
|
||||
}
|
||||
if((modLoaderVersion = dependencies.get("fabric-loader")) != null) {
|
||||
return new ModLoaderInfo(ModLoaderInfo.MOD_LOADER_FABRIC, modLoaderVersion, mcVersion);
|
||||
}
|
||||
if((modLoaderVersion = dependencies.get("quilt-loader")) != null) {
|
||||
throw new RuntimeException("Quilt is gay af");
|
||||
//return new ModLoaderInfo(ModLoaderInfo.MOD_LOADER_QUILT, modLoaderVersion, mcVersion);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ModrinthIndex installMrpack(File mrpackFile, File instanceDestination) throws IOException {
|
||||
try (ZipFile modpackZipFile = new ZipFile(mrpackFile)){
|
||||
ModrinthIndex modrinthIndex = Tools.GLOBAL_GSON.fromJson(
|
||||
Tools.read(ZipUtils.getEntryStream(modpackZipFile, "modrinth.index.json")),
|
||||
ModrinthIndex.class);
|
||||
|
||||
ModDownloader modDownloader = new ModDownloader(instanceDestination);
|
||||
for(ModrinthIndex.ModrinthIndexFile indexFile : modrinthIndex.files) {
|
||||
modDownloader.submitDownload(indexFile.fileSize, indexFile.path, indexFile.downloads);
|
||||
}
|
||||
modDownloader.awaitFinish(new DownloaderProgressWrapper(R.string.modpack_download_downloading_mods, ProgressLayout.INSTALL_MODPACK));
|
||||
ProgressLayout.setProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.modpack_download_applying_overrides, 1, 2);
|
||||
ZipUtils.zipExtract(modpackZipFile, "overrides/", instanceDestination);
|
||||
ProgressLayout.setProgress(ProgressLayout.INSTALL_MODPACK, 50, R.string.modpack_download_applying_overrides, 2, 2);
|
||||
ZipUtils.zipExtract(modpackZipFile, "client-overrides/", instanceDestination);
|
||||
return modrinthIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.imagecache;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -70,4 +75,32 @@ public class ModIconCache {
|
||||
if(isCanceled) Log.i("IconCache", "checkCancelled("+imageReceiver.hashCode()+") == true");
|
||||
return isCanceled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base64-encoded version of a cached icon by its tag.
|
||||
* Note: this functions performs I/O operations, and should not be called on the UI
|
||||
* thread.
|
||||
* @param imageTag the icon tag
|
||||
* @return the base64 encoded image or null if not cached
|
||||
*/
|
||||
|
||||
public static String getBase64Image(String imageTag) {
|
||||
File imagePath = new File(Tools.DIR_CACHE, "mod_icons/"+imageTag+".ca");
|
||||
Log.i("IconCache", "Creating base64 version of icon "+imageTag);
|
||||
if(!imagePath.canRead() || !imagePath.isFile()) {
|
||||
Log.i("IconCache", "Icon does not exist");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
try(FileInputStream fileInputStream = new FileInputStream(imagePath)) {
|
||||
byte[] imageBytes = IOUtils.toByteArray(fileInputStream);
|
||||
// reencode to png? who cares! our profile icon cache is an omnivore!
|
||||
// if some other launcher parses this and dies it is not our problem :troll:
|
||||
return "data:image/png;base64,"+ Base64.encodeToString(imageBytes, Base64.DEFAULT);
|
||||
}
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.models;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ModDetail extends ModItem {
|
||||
@@ -15,6 +17,7 @@ public class ModDetail extends ModItem {
|
||||
this.versionUrls = versionUrls;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModDetail{" +
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ModItem extends ModSource {
|
||||
|
||||
public String id;
|
||||
@@ -16,6 +18,7 @@ public class ModItem extends ModSource {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModItem{" +
|
||||
@@ -27,4 +30,8 @@ public class ModItem extends ModSource {
|
||||
", isModpack=" + isModpack +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String getIconCacheTag() {
|
||||
return apiSource+"_"+id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package net.kdt.pojavlaunch.modloaders.modpacks.models;
|
||||
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* POJO to represent the modrinth index inside mrpacks
|
||||
@@ -20,6 +21,7 @@ public class ModrinthIndex {
|
||||
public String summary;
|
||||
|
||||
public ModrinthIndexFile[] files;
|
||||
public Map<String, String> dependencies;
|
||||
|
||||
|
||||
public static class ModrinthIndexFile {
|
||||
@@ -31,6 +33,7 @@ public class ModrinthIndex {
|
||||
|
||||
@Nullable public ModrinthIndexFileEnv env;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModrinthIndexFile{" +
|
||||
@@ -45,6 +48,7 @@ public class ModrinthIndex {
|
||||
public String sha1;
|
||||
public String sha512;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModrinthIndexFileHashes{" +
|
||||
@@ -58,6 +62,7 @@ public class ModrinthIndex {
|
||||
public String client;
|
||||
public String server;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModrinthIndexFileEnv{" +
|
||||
@@ -68,27 +73,7 @@ public class ModrinthIndex {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class ModrinthIndexDependencies {
|
||||
@Nullable public String minecraft;
|
||||
@Nullable public String forge;
|
||||
@SerializedName("fabric-loader")
|
||||
@Nullable public String fabricLoader;
|
||||
@SerializedName("quilt-loader")
|
||||
@Nullable public String quiltLoader;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModrinthIndexDependencies{" +
|
||||
"minecraft='" + minecraft + '\'' +
|
||||
", forge='" + forge + '\'' +
|
||||
", fabricLoader='" + fabricLoader + '\'' +
|
||||
", quiltLoader='" + quiltLoader + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ModrinthIndex{" +
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.kdt.pojavlaunch.progresskeeper;
|
||||
|
||||
import static net.kdt.pojavlaunch.Tools.BYTE_TO_MB;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
public class DownloaderProgressWrapper implements Tools.DownloaderFeedback {
|
||||
|
||||
private final int mProgressString;
|
||||
private final String mProgressRecord;
|
||||
public String extraString = null;
|
||||
|
||||
/**
|
||||
* A simple wrapper to send the downloader progress to ProgressKeeper
|
||||
* @param progressString the string that will be used in the progress reporter
|
||||
* @param progressRecord the record for ProgressKeeper
|
||||
*/
|
||||
public DownloaderProgressWrapper(int progressString, String progressRecord) {
|
||||
this.mProgressString = progressString;
|
||||
this.mProgressRecord = progressRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int curr, int max) {
|
||||
Object[] va;
|
||||
if(extraString != null) {
|
||||
va = new Object[3];
|
||||
va[0] = extraString;
|
||||
va[1] = curr/BYTE_TO_MB;
|
||||
va[2] = max/BYTE_TO_MB;
|
||||
}
|
||||
else {
|
||||
va = new Object[2];
|
||||
va[0] = curr/BYTE_TO_MB;
|
||||
va[1] = max/BYTE_TO_MB;
|
||||
}
|
||||
// the allocations are fine because thats how java implements variadic arguments in bytecode: an array of whatever
|
||||
ProgressKeeper.submitProgress(mProgressRecord, (int) Math.max((float)curr/max*100,0), mProgressString, va);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.kdt.pojavlaunch.tasks;
|
||||
|
||||
import static net.kdt.pojavlaunch.PojavApplication.sExecutorService;
|
||||
import static net.kdt.pojavlaunch.Tools.BYTE_TO_MB;
|
||||
import static net.kdt.pojavlaunch.utils.DownloadUtils.downloadFileMonitored;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -40,7 +41,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class AsyncMinecraftDownloader {
|
||||
private static final float BYTE_TO_MB = 1024 * 1024;
|
||||
|
||||
public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/";
|
||||
|
||||
/* Allows each downloading thread to have its own RECYCLED buffer */
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.kdt.pojavlaunch.utils;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class ZipUtils {
|
||||
/**
|
||||
* Gets an InputStream for a given ZIP entry, throwing an IOException if the ZIP entry does not
|
||||
* exist.
|
||||
* @param zipFile The ZipFile to get the entry from
|
||||
* @param entryPath The full path inside of the ZipFile
|
||||
* @return The InputStream provided by the ZipFile
|
||||
* @throws IOException if the entry was not found
|
||||
*/
|
||||
public static InputStream getEntryStream(ZipFile zipFile, String entryPath) throws IOException{
|
||||
ZipEntry entry = zipFile.getEntry(entryPath);
|
||||
if(entry == null) throw new IOException("No entry in ZIP file: "+entryPath);
|
||||
return zipFile.getInputStream(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all files in a ZipFile inside of a given directory to a given destination directory
|
||||
* How to specify dirName:
|
||||
* If you want to extract all files in the ZipFile, specify ""
|
||||
* If you want to extract a single directory, specify its full path followed by a trailing /
|
||||
* @param zipFile The ZipFile to extract files from
|
||||
* @param dirName The directory to extract the files from
|
||||
* @param destination The destination directory to extract the files into
|
||||
* @throws IOException if it was not possible to create a directory or file extraction failed
|
||||
*/
|
||||
public static void zipExtract(ZipFile zipFile, String dirName, File destination) throws IOException {
|
||||
Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
|
||||
|
||||
int dirNameLen = dirName.length();
|
||||
while(zipEntries.hasMoreElements()) {
|
||||
ZipEntry zipEntry = zipEntries.nextElement();
|
||||
String entryName = zipEntry.getName();
|
||||
if(!entryName.startsWith(dirName) || zipEntry.isDirectory()) continue;
|
||||
File zipDestination = new File(destination, entryName.substring(dirNameLen));
|
||||
File parent = zipDestination.getParentFile();
|
||||
if(parent != null && !parent.exists())
|
||||
if(!parent.mkdirs()) throw new IOException("Failed to create "+parent.getAbsolutePath());
|
||||
try (InputStream inputStream = zipFile.getInputStream(zipEntry);
|
||||
OutputStream outputStream =
|
||||
new FileOutputStream(zipDestination)) {
|
||||
IOUtils.copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,4 +77,15 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/search_mod_selected_mc_version_textview"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_mod_status_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TextView"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/search_mod_list"
|
||||
app:layout_constraintEnd_toEndOf="@+id/search_mod_list"
|
||||
app:layout_constraintStart_toStartOf="@+id/search_mod_list"
|
||||
app:layout_constraintTop_toTopOf="@+id/search_mod_list" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -410,5 +410,9 @@
|
||||
|
||||
<string name="generic_install">Install</string>
|
||||
|
||||
|
||||
<string name="search_modpack_no_result">No modpacks found</string>
|
||||
<string name="search_modpack_error">Failed to find modpacks</string>
|
||||
<string name="modpack_download_downloading_metadata">Downloading modpack metadata (%.2f MB / %.2f MB)</string>
|
||||
<string name="modpack_download_downloading_mods">Downloading mods (%.2f MB / %.2f MB)</string>
|
||||
<string name="modpack_download_applying_overrides">Applying overrides (%d/%d)</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user