Add comments, minor refactor
This commit is contained in:
parent
b4e00c4ac2
commit
ff41704101
|
@ -1,14 +1,31 @@
|
|||
package com.zane.smapiinstaller.constant;
|
||||
|
||||
/**
|
||||
* 常量
|
||||
*/
|
||||
public class Constants {
|
||||
/**
|
||||
* Mod安装路径
|
||||
*/
|
||||
public static final String MOD_PATH = "StardewValley/Mods";
|
||||
/**
|
||||
* 日志路径
|
||||
*/
|
||||
public static final String LOG_PATH = "StardewValley/ErrorLogs/SMAPI-latest.txt";
|
||||
|
||||
/**
|
||||
* 安装包目标包名
|
||||
*/
|
||||
public static final String TARGET_PACKAGE_NAME = "com.zane.stardewvalley";
|
||||
|
||||
/**
|
||||
* DLC下载路径
|
||||
*/
|
||||
public static final String DLC_LIST_UPDATE_URL = "http://dl.zaneyork.cn/smapi/downloadable_content_list.json";
|
||||
|
||||
/**
|
||||
* 帮助内容下载路径
|
||||
*/
|
||||
public static final String HELP_LIST_UPDATE_URL = "http://dl.zaneyork.cn/smapi/help_item_list.json";
|
||||
|
||||
/**
|
||||
* AppCenter秘钥
|
||||
*/
|
||||
public static final String APP_CENTER_SECRET = "cb44e94a-7b2f-431e-9ad9-48013ec8c208";
|
||||
}
|
||||
|
|
|
@ -4,10 +4,25 @@ import java.util.List;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* SMAPI所需处理的文件清单
|
||||
*/
|
||||
@Data
|
||||
public class ApkFilesManifest {
|
||||
/**
|
||||
* 最小兼容版本,包含
|
||||
*/
|
||||
private long minBuildCode;
|
||||
/**
|
||||
* 最大兼容版本,包含
|
||||
*/
|
||||
private Long maxBuildCode;
|
||||
/**
|
||||
* 兼容包基础文件路径
|
||||
*/
|
||||
private String basePath;
|
||||
/**
|
||||
* 文件清单
|
||||
*/
|
||||
private List<ManifestEntry> manifestEntries;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,33 @@ package com.zane.smapiinstaller.entity;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 可下载内容包
|
||||
*/
|
||||
@Data
|
||||
public class DownloadableContent {
|
||||
/**
|
||||
* 类型,COMPAT:兼容包/LOCALE:语言包
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 资源存放位置
|
||||
*/
|
||||
private String assetPath;
|
||||
/**
|
||||
* 下载位置
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 文件SHA3-256校验码
|
||||
*/
|
||||
private String hash;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,14 @@ import java.util.List;
|
|||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 可下载内容列表
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DownloadableContentList extends UpdatableList {
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
List<DownloadableContent> contents;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,21 @@ package com.zane.smapiinstaller.entity;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 帮助信息
|
||||
*/
|
||||
@Data
|
||||
public class HelpItem {
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 作者
|
||||
*/
|
||||
private String author;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,14 @@ import java.util.List;
|
|||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 帮助内容列表
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class HelpItemList extends UpdatableList {
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
private List<HelpItem> items;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,29 @@ package com.zane.smapiinstaller.entity;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 文件映射关系
|
||||
*/
|
||||
@Data
|
||||
public class ManifestEntry {
|
||||
/**
|
||||
* 目标路径
|
||||
*/
|
||||
private String targetPath;
|
||||
/**
|
||||
* 资源路径
|
||||
*/
|
||||
private String assetPath;
|
||||
/**
|
||||
* 压缩级别
|
||||
*/
|
||||
private int compression;
|
||||
/**
|
||||
* 来源位置,0:安装器自带/1:从APK中抽取
|
||||
*/
|
||||
private int origin;
|
||||
/**
|
||||
* 文件是否不属于兼容包中
|
||||
*/
|
||||
private boolean external;
|
||||
}
|
||||
|
|
|
@ -4,16 +4,46 @@ import java.util.Set;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Mod信息
|
||||
*/
|
||||
@Data
|
||||
public class ModManifestEntry {
|
||||
/**
|
||||
* 存放位置
|
||||
*/
|
||||
private String assetPath;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String Name;
|
||||
/**
|
||||
* 唯一ID
|
||||
*/
|
||||
private String UniqueID;
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
private String Version;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String Description;
|
||||
/**
|
||||
* 依赖
|
||||
*/
|
||||
private Set<ModManifestEntry> Dependencies;
|
||||
/**
|
||||
* 资源包类型
|
||||
*/
|
||||
private ModManifestEntry ContentPackFor;
|
||||
|
||||
/**
|
||||
* 最小依赖版本
|
||||
*/
|
||||
private String MinimumVersion;
|
||||
/**
|
||||
* 是否必须依赖
|
||||
*/
|
||||
private Boolean IsRequired;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,13 @@ package com.zane.smapiinstaller.entity;
|
|||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 可更新列表
|
||||
*/
|
||||
@Data
|
||||
public class UpdatableList {
|
||||
/**
|
||||
* 列表版本
|
||||
*/
|
||||
private int version;
|
||||
}
|
||||
|
|
|
@ -57,6 +57,10 @@ public class ApkPatcher {
|
|||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 依次扫描package_names.json文件对应的包名,抽取找到的第一个游戏APK到SMAPI Installer路径
|
||||
* @return 抽取后的APK文件路径,如果抽取失败返回null
|
||||
*/
|
||||
public String extract() {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
List<String> packageNames = FileUtils.getAssetJson(context, "package_names.json", new TypeReference<List<String>>() { });
|
||||
|
@ -91,6 +95,11 @@ public class ApkPatcher {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定APK文件重新打包,添加SMAPI,修改AndroidManifest.xml,同时验证版本是否正确
|
||||
* @param apkPath APK文件路径
|
||||
* @return 是否成功打包
|
||||
*/
|
||||
public boolean patch(String apkPath) {
|
||||
if (apkPath == null)
|
||||
return false;
|
||||
|
@ -130,6 +139,12 @@ public class ApkPatcher {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描全部兼容包,寻找匹配的版本,修改AndroidManifest.xml文件
|
||||
* @param bytes AndroidManifest.xml的字节数组
|
||||
* @param manifests 兼容包列表
|
||||
* @return 修改后的AndroidManifest.xml的字节数组
|
||||
*/
|
||||
private byte[] modifyManifest(byte[] bytes, List<ApkFilesManifest> manifests) {
|
||||
AtomicReference<String> packageName = new AtomicReference<>();
|
||||
Predicate<ManifestTagVisitor.AttrArgs> processLogic = (attr) -> {
|
||||
|
@ -184,6 +199,11 @@ public class ApkPatcher {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新签名安装包
|
||||
* @param apkPath APK文件路径
|
||||
* @return 签名后的安装包路径
|
||||
*/
|
||||
public String sign(String apkPath) {
|
||||
try {
|
||||
File externalFilesDir = Environment.getExternalStorageDirectory();
|
||||
|
@ -207,6 +227,10 @@ public class ApkPatcher {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对指定安装包发起安装
|
||||
* @param apkPath 安装包路径
|
||||
*/
|
||||
public void install(String apkPath) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(fromFile(new File(apkPath)), "application/vnd.android.package-archive");
|
||||
|
@ -234,6 +258,10 @@ public class ApkPatcher {
|
|||
return Uri.fromFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报错内容
|
||||
* @return 报错内容
|
||||
*/
|
||||
public AtomicReference<String> getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
|
|
@ -37,18 +37,15 @@ import pxb.android.axml.AxmlVisitor;
|
|||
import pxb.android.axml.AxmlWriter;
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
/**
|
||||
* 通用逻辑
|
||||
*/
|
||||
public class CommonLogic {
|
||||
|
||||
public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing() && !dialog.isCancelled()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
dialog.incrementProgress(progress - dialog.getCurrentProgress());
|
||||
dialog.setContent(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从View获取所属Activity
|
||||
* @param view context容器
|
||||
* @return Activity
|
||||
*/
|
||||
public static Activity getActivityFromView(View view) {
|
||||
if (null != view) {
|
||||
Context context = view.getContext();
|
||||
|
@ -62,51 +59,12 @@ public class CommonLogic {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static void showAlertDialog(View view, int title, String message) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show());
|
||||
}
|
||||
}
|
||||
|
||||
public static void showAlertDialog(View view, int title, int message) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show());
|
||||
}
|
||||
}
|
||||
|
||||
public static void showConfirmDialog(View view, int title, int message, MaterialDialog.SingleButtonCallback callback) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show());
|
||||
}
|
||||
}
|
||||
|
||||
public static void showConfirmDialog(View view, int title, String message, MaterialDialog.SingleButtonCallback callback) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show());
|
||||
}
|
||||
}
|
||||
|
||||
public static AtomicReference<MaterialDialog> showProgressDialog(View view, int title, String message) {
|
||||
Activity activity = getActivityFromView(view);
|
||||
AtomicReference<MaterialDialog> reference = new AtomicReference<>();
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(activity)
|
||||
.title(title)
|
||||
.content(message)
|
||||
.progress(false, 100, true)
|
||||
.cancelable(false)
|
||||
.show();
|
||||
reference.set(dialog);
|
||||
});
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开指定URL
|
||||
* @param context context
|
||||
* @param url 目标URL
|
||||
*/
|
||||
public static void openUrl(Context context, String url) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
|
@ -118,6 +76,12 @@ public class CommonLogic {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文本到剪贴板
|
||||
* @param context 上下文
|
||||
* @param copyStr 文本
|
||||
* @return 是否复制成功
|
||||
*/
|
||||
public static boolean copyToClipboard(Context context, String copyStr) {
|
||||
try {
|
||||
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
|
@ -129,6 +93,11 @@ public class CommonLogic {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描全部兼容包
|
||||
* @param context context
|
||||
* @return 兼容包列表
|
||||
*/
|
||||
public static List<ApkFilesManifest> findAllApkFileManifest(Context context) {
|
||||
ApkFilesManifest apkFilesManifest = com.zane.smapiinstaller.utils.FileUtils.getAssetJson(context, "apk_files_manifest.json", ApkFilesManifest.class);
|
||||
ArrayList<ApkFilesManifest> apkFilesManifests = Lists.newArrayList(apkFilesManifest);
|
||||
|
@ -148,7 +117,14 @@ public class CommonLogic {
|
|||
return apkFilesManifests;
|
||||
}
|
||||
|
||||
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMod) {
|
||||
/**
|
||||
* 提取SMAPI环境文件到内部存储对应位置
|
||||
* @param context context
|
||||
* @param apkPath 安装包路径
|
||||
* @param checkMode 是否为校验模式
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
public static boolean unpackSmapiFiles(Context context, String apkPath, boolean checkMode) {
|
||||
List<ManifestEntry> manifestEntries = com.zane.smapiinstaller.utils.FileUtils.getAssetJson(context, "smapi_files_manifest.json", new TypeReference<List<ManifestEntry>>() { });
|
||||
if (manifestEntries == null)
|
||||
return false;
|
||||
|
@ -169,7 +145,7 @@ public class CommonLogic {
|
|||
File targetFile = new File(basePath, entry.getTargetPath());
|
||||
switch (entry.getOrigin()) {
|
||||
case 0:
|
||||
if (!checkMod || !targetFile.exists()) {
|
||||
if (!checkMode || !targetFile.exists()) {
|
||||
try (InputStream inputStream = context.getAssets().open(entry.getAssetPath())) {
|
||||
if (!targetFile.getParentFile().exists()) {
|
||||
if (!targetFile.getParentFile().mkdirs()) {
|
||||
|
@ -185,7 +161,7 @@ public class CommonLogic {
|
|||
}
|
||||
break;
|
||||
case 1:
|
||||
if (!checkMod || !targetFile.exists()) {
|
||||
if (!checkMode || !targetFile.exists()) {
|
||||
ZipUtil.unpackEntry(new File(apkPath), entry.getAssetPath(), targetFile);
|
||||
}
|
||||
break;
|
||||
|
@ -194,6 +170,13 @@ public class CommonLogic {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AndroidManifest.xml文件
|
||||
* @param bytes AndroidManifest.xml文件字符数组
|
||||
* @param processLogic 处理逻辑
|
||||
* @return 修改后的AndroidManifest.xml文件字符数组
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public static byte[] modifyManifest(byte[] bytes, Predicate<ManifestTagVisitor.AttrArgs> processLogic) throws IOException {
|
||||
AxmlReader reader = new AxmlReader(bytes);
|
||||
AxmlWriter writer = new AxmlWriter();
|
||||
|
|
|
@ -9,7 +9,11 @@ import android.view.View;
|
|||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
||||
/**
|
||||
* 游戏启动器
|
||||
*/
|
||||
public class GameLauncher {
|
||||
|
||||
private final View root;
|
||||
|
@ -18,13 +22,16 @@ public class GameLauncher {
|
|||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动逻辑
|
||||
*/
|
||||
public void launch() {
|
||||
Activity context = CommonLogic.getActivityFromView(root);
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(Constants.TARGET_PACKAGE_NAME, 0);
|
||||
if(!CommonLogic.unpackSmapiFiles(context, packageInfo.applicationInfo.publicSourceDir, true)) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair);
|
||||
DialogUtils.showAlertDialog(root, R.string.error, R.string.error_failed_to_repair);
|
||||
return;
|
||||
}
|
||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||
|
@ -35,10 +42,10 @@ public class GameLauncher {
|
|||
}
|
||||
});
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed);
|
||||
DialogUtils.showAlertDialog(root, R.string.error, R.string.error_smapi_not_installed);
|
||||
} catch (Exception e) {
|
||||
Crashes.trackError(e);
|
||||
CommonLogic.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
|
||||
DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import com.google.common.base.Predicate;
|
|||
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
/**
|
||||
* AndroidManifest文件节点访问器
|
||||
*/
|
||||
class ManifestTagVisitor extends NodeVisitor {
|
||||
|
||||
private final Predicate<AttrArgs> attrProcessLogic;
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.google.common.collect.Queues;
|
|||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.constant.Constants;
|
||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
import com.zane.smapiinstaller.utils.FileUtils;
|
||||
import com.zane.smapiinstaller.utils.VersionUtil;
|
||||
|
||||
|
@ -32,6 +33,9 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
/**
|
||||
* Mod资源管理器
|
||||
*/
|
||||
public class ModAssetsManager {
|
||||
|
||||
private final View root;
|
||||
|
@ -42,6 +46,11 @@ public class ModAssetsManager {
|
|||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找第一个匹配的Mod
|
||||
* @param filter 过滤规则
|
||||
* @return Mod信息
|
||||
*/
|
||||
public static ModManifestEntry findFirstModIf(Predicate<ModManifestEntry> filter) {
|
||||
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
|
||||
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH));
|
||||
|
@ -70,6 +79,10 @@ public class ModAssetsManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找全部已识别Mod
|
||||
* @return Mod信息列表
|
||||
*/
|
||||
public static List<ModManifestEntry> findAllInstalledMods() {
|
||||
ConcurrentLinkedQueue<File> files = Queues.newConcurrentLinkedQueue();
|
||||
files.add(new File(Environment.getExternalStorageDirectory(), Constants.MOD_PATH));
|
||||
|
@ -103,6 +116,10 @@ public class ModAssetsManager {
|
|||
return mods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装默认Mod
|
||||
* @return 是否安装成功
|
||||
*/
|
||||
public boolean installDefaultMods() {
|
||||
Activity context = CommonLogic.getActivityFromView(root);
|
||||
List<ModManifestEntry> modManifestEntries = FileUtils.getAssetJson(context, "mods_manifest.json", new TypeReference<List<ModManifestEntry>>() { });
|
||||
|
@ -114,7 +131,7 @@ public class ModAssetsManager {
|
|||
if (installedModMap.containsKey(mod.getUniqueID()) || installedModMap.containsKey(mod.getUniqueID().replace("ZaneYork.CustomLocalization", "SMAPI.CustomLocalization"))) {
|
||||
ImmutableList<ModManifestEntry> installedMods = installedModMap.get(mod.getUniqueID());
|
||||
if (installedMods.size() > 1) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error,
|
||||
DialogUtils.showAlertDialog(root, R.string.error,
|
||||
String.format(context.getString(R.string.duplicate_mod_found),
|
||||
Joiner.on(",").join(Lists.transform(installedMods, item -> FileUtils.toPrettyPath(item.getAssetPath())))));
|
||||
return false;
|
||||
|
@ -139,6 +156,10 @@ public class ModAssetsManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Mod环境
|
||||
* @param returnCallback 回调函数
|
||||
*/
|
||||
public void checkModEnvironment(Consumer<Boolean> returnCallback) {
|
||||
ImmutableListMultimap<String, ModManifestEntry> installedModMap = Multimaps.index(findAllInstalledMods(), ModManifestEntry::getUniqueID);
|
||||
checkDuplicateMod(installedModMap, (isConfirm) -> {
|
||||
|
@ -154,6 +175,11 @@ public class ModAssetsManager {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有重复Mod
|
||||
* @param installedModMap 已安装Mod集合
|
||||
* @param returnCallback 回调函数
|
||||
*/
|
||||
private void checkDuplicateMod(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> returnCallback) {
|
||||
// Duplicate mod check
|
||||
ArrayList<String> list = Lists.newArrayList();
|
||||
|
@ -164,7 +190,7 @@ public class ModAssetsManager {
|
|||
}
|
||||
}
|
||||
if (list.size() > 0) {
|
||||
CommonLogic.showConfirmDialog(root, R.string.error,
|
||||
DialogUtils.showConfirmDialog(root, R.string.error,
|
||||
root.getContext().getString(R.string.duplicate_mod_found, Joiner.on(";").join(list)),
|
||||
((dialog, which) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
|
@ -178,6 +204,11 @@ public class ModAssetsManager {
|
|||
returnCallback.accept(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有依赖关系缺失
|
||||
* @param installedModMap 已安装Mod集合
|
||||
* @param returnCallback 回调函数
|
||||
*/
|
||||
private void checkUnsatisfiedDependencies(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> returnCallback) {
|
||||
Iterable<String> dependencyErrors = Iterables.filter(Iterables.transform(installedModMap.values(), mod -> {
|
||||
if (mod.getDependencies() != null) {
|
||||
|
@ -216,7 +247,7 @@ public class ModAssetsManager {
|
|||
return null;
|
||||
}), item -> item != null);
|
||||
if (dependencyErrors.iterator().hasNext()) {
|
||||
CommonLogic.showConfirmDialog(root, R.string.error,
|
||||
DialogUtils.showConfirmDialog(root, R.string.error,
|
||||
Joiner.on(";").join(dependencyErrors),
|
||||
((dialog, which) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
|
@ -230,6 +261,11 @@ public class ModAssetsManager {
|
|||
returnCallback.accept(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有资源包依赖Mod没有安装
|
||||
* @param installedModMap 已安装Mod集合
|
||||
* @param returnCallback 回调函数
|
||||
*/
|
||||
private void checkContentpacks(ImmutableListMultimap<String, ModManifestEntry> installedModMap, Consumer<Boolean> returnCallback) {
|
||||
Iterable<String> dependencyErrors = Iterables.filter(Iterables.transform(installedModMap.values(), mod -> {
|
||||
ModManifestEntry dependency = mod.getContentPackFor();
|
||||
|
@ -263,7 +299,7 @@ public class ModAssetsManager {
|
|||
return null;
|
||||
}), item -> item != null);
|
||||
if (dependencyErrors.iterator().hasNext()) {
|
||||
CommonLogic.showConfirmDialog(root, R.string.error,
|
||||
DialogUtils.showConfirmDialog(root, R.string.error,
|
||||
Joiner.on(";").join(dependencyErrors),
|
||||
((dialog, which) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
|
|
|
@ -13,6 +13,10 @@ import com.zane.smapiinstaller.utils.JSONUtil;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 在线列表更新管理器
|
||||
* @param <T> 列表类型
|
||||
*/
|
||||
public class UpdatableListManager<T extends UpdatableList> {
|
||||
private static boolean updateChecked = false;
|
||||
|
||||
|
@ -20,6 +24,12 @@ public class UpdatableListManager<T extends UpdatableList> {
|
|||
|
||||
private List<Predicate<T>> onChangedListener = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param root context容器
|
||||
* @param filename 本地文件名
|
||||
* @param tClass 目标类型
|
||||
* @param updateUrl 更新地址
|
||||
*/
|
||||
public UpdatableListManager(View root, String filename, Class<T> tClass, String updateUrl) {
|
||||
updatableList = FileUtils.getAssetJson(root.getContext(), filename, tClass);
|
||||
if(!updateChecked) {
|
||||
|
@ -40,10 +50,17 @@ public class UpdatableListManager<T extends UpdatableList> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 列表
|
||||
*/
|
||||
public T getList() {
|
||||
return (T) updatableList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册列表变化监听器
|
||||
* @param onChanged 回调
|
||||
*/
|
||||
public void registerListChangeListener(Predicate<T> onChanged) {
|
||||
this.onChangedListener.add(onChanged);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import android.widget.Button;
|
|||
import android.widget.EditText;
|
||||
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
import com.zane.smapiinstaller.utils.FileUtils;
|
||||
import com.zane.smapiinstaller.utils.JSONUtil;
|
||||
|
||||
|
@ -62,7 +62,7 @@ public class ConfigEditFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
CommonLogic.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage());
|
||||
DialogUtils.showAlertDialog(getView(), R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import android.widget.TextView;
|
|||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.commons.FileUtils;
|
||||
|
@ -76,7 +76,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
|||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
@OnClick(R.id.button_remove_mod) void removeMod() {
|
||||
CommonLogic.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which)->{
|
||||
DialogUtils.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which)->{
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
File file = new File(modPath);
|
||||
if (file.exists()) {
|
||||
|
@ -87,7 +87,7 @@ public class ModManifestAdapter extends RecyclerView.Adapter<ModManifestAdapter.
|
|||
notifyItemRemoved(idx);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, e.getMessage());
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ import com.lzy.okgo.model.Response;
|
|||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.entity.DownloadableContent;
|
||||
import com.zane.smapiinstaller.entity.ModManifestEntry;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
@ -109,12 +109,12 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
if (StringUtils.isNoneBlank(downloadableContent.getAssetPath())) {
|
||||
File contentFile = new File(itemView.getContext().getFilesDir(), downloadableContent.getAssetPath());
|
||||
if (contentFile.exists()) {
|
||||
CommonLogic.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which) -> {
|
||||
DialogUtils.showConfirmDialog(itemView, R.string.confirm, R.string.confirm_delete_content, (dialog, which) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
try {
|
||||
FileUtils.forceDelete(contentFile);
|
||||
} catch (IOException e) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage());
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -129,7 +129,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
if (StringUtils.equals(downloadableContent.getType(), "LOCALE")) {
|
||||
modManifestEntry = ModAssetsManager.findFirstModIf(mod -> StringUtils.equals(mod.getUniqueID(), "ZaneYork.CustomLocalization") || StringUtils.equals(mod.getUniqueID(), "SMAPI.CustomLocalization"));
|
||||
if (modManifestEntry == null) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, String.format(context.getString(R.string.error_depends_on_mod), context.getString(R.string.locale_pack), "ZaneYork.CustomLocalization"));
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, String.format(context.getString(R.string.error_depends_on_mod), context.getString(R.string.locale_pack), "ZaneYork.CustomLocalization"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
return;
|
||||
downloading.set(true);
|
||||
ModManifestEntry finalModManifestEntry = modManifestEntry;
|
||||
AtomicReference<MaterialDialog> dialogRef = CommonLogic.showProgressDialog(itemView, R.string.progress, "");
|
||||
AtomicReference<MaterialDialog> dialogRef = DialogUtils.showProgressDialog(itemView, R.string.progress, "");
|
||||
OkGo.<File>get(downloadableContent.getUrl()).execute(new FileCallback(file.getParentFile().getAbsolutePath(), file.getName()) {
|
||||
@Override
|
||||
public void onError(Response<File> response) {
|
||||
|
@ -156,7 +156,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
dialog.dismiss();
|
||||
}
|
||||
downloading.set(false);
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -179,7 +179,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
File downloadedFile = response.body();
|
||||
String hash = com.zane.smapiinstaller.utils.FileUtils.getFileHash(downloadedFile);
|
||||
if (!StringUtils.equalsIgnoreCase(hash, downloadableContent.getHash())) {
|
||||
CommonLogic.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
|
||||
DialogUtils.showAlertDialog(itemView, R.string.error, R.string.error_failed_to_download);
|
||||
return;
|
||||
}
|
||||
unpackLogic(context, downloadedFile, finalModManifestEntry);
|
||||
|
@ -195,7 +195,7 @@ public class DownloadableContentAdapter extends RecyclerView.Adapter<Downloadabl
|
|||
} else {
|
||||
ZipUtil.unpack(downloadedFile, new File(context.getFilesDir(), downloadableContent.getAssetPath()));
|
||||
}
|
||||
CommonLogic.showAlertDialog(itemView, R.string.info, R.string.download_unpack_success);
|
||||
DialogUtils.showAlertDialog(itemView, R.string.info, R.string.download_unpack_success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ import android.view.ViewGroup;
|
|||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.GravityEnum;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.microsoft.appcenter.AppCenter;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.logic.ApkPatcher;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
import com.zane.smapiinstaller.logic.ModAssetsManager;
|
||||
import com.zane.smapiinstaller.utils.DialogUtils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class InstallFragment extends Fragment {
|
|||
@OnClick(R.id.button_install)
|
||||
void Install() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
CommonLogic.showConfirmDialog(root, R.string.confirm, R.string.android_version_confirm, ((dialog, which) -> {
|
||||
DialogUtils.showConfirmDialog(root, R.string.confirm, R.string.android_version_confirm, ((dialog, which) -> {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
installLogic();
|
||||
}
|
||||
|
@ -53,6 +53,9 @@ public class InstallFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装逻辑
|
||||
*/
|
||||
private void installLogic() {
|
||||
new MaterialDialog.Builder(context).title(R.string.install_progress_title).content(R.string.extracting_package).contentGravity(GravityEnum.CENTER)
|
||||
.progress(false, 100, true).cancelable(false).cancelListener(dialog -> {
|
||||
|
@ -67,39 +70,39 @@ public class InstallFragment extends Fragment {
|
|||
task = new Thread(() -> {
|
||||
try {
|
||||
ApkPatcher patcher = new ApkPatcher(context);
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.extracting_package, 0);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.extracting_package, 0);
|
||||
String path = patcher.extract();
|
||||
if (path == null) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.error_game_not_found)));
|
||||
return;
|
||||
}
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 10);
|
||||
if (!CommonLogic.unpackSmapiFiles(context, path, false)) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_unpack_smapi_files)));
|
||||
return;
|
||||
}
|
||||
ModAssetsManager modAssetsManager = new ModAssetsManager(root);
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 15);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.unpacking_smapi_files, 15);
|
||||
modAssetsManager.installDefaultMods();
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.patching_package, 25);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.patching_package, 25);
|
||||
if (!patcher.patch(path)) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game)));
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_patch_game)));
|
||||
return;
|
||||
}
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.signing_package, 55);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.signing_package, 55);
|
||||
String signPath = patcher.sign(path);
|
||||
if (signPath == null) {
|
||||
CommonLogic.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
||||
DialogUtils.showAlertDialog(root, R.string.error, StringUtils.firstNonBlank(patcher.getErrorMessage().get(), context.getString(R.string.failed_to_sign_game)));
|
||||
return;
|
||||
}
|
||||
CommonLogic.setProgressDialogState(root, dialog, R.string.installing_package, 99);
|
||||
DialogUtils.setProgressDialogState(root, dialog, R.string.installing_package, 99);
|
||||
patcher.install(signPath);
|
||||
dialog.incrementProgress(1);
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
Crashes.trackError(e);
|
||||
CommonLogic.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
|
||||
DialogUtils.showAlertDialog(root, R.string.error, e.getLocalizedMessage());
|
||||
}
|
||||
finally {
|
||||
if (!dialog.isCancelled()) {
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package com.zane.smapiinstaller.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.zane.smapiinstaller.R;
|
||||
import com.zane.smapiinstaller.logic.CommonLogic;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 对话框相关工具类
|
||||
*/
|
||||
public class DialogUtils {
|
||||
/**
|
||||
* 设置进度条状态
|
||||
* @param view context容器
|
||||
* @param dialog 对话框
|
||||
* @param message 消息
|
||||
* @param progress 进度
|
||||
*/
|
||||
public static void setProgressDialogState(View view, MaterialDialog dialog, int message, int progress) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing() && !dialog.isCancelled()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
dialog.setProgress(progress);
|
||||
dialog.setContent(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示警告对话框
|
||||
* @param view context容器
|
||||
* @param title 标题
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void showAlertDialog(View view, int title, String message) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示警告对话框
|
||||
* @param view context容器
|
||||
* @param title 标题
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void showAlertDialog(View view, int title, int message) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.ok).show());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示确认对话框
|
||||
* @param view context容器
|
||||
* @param title 标题
|
||||
* @param message 消息
|
||||
* @param callback 回调
|
||||
*/
|
||||
public static void showConfirmDialog(View view, int title, int message, MaterialDialog.SingleButtonCallback callback) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示确认对话框
|
||||
* @param view context容器
|
||||
* @param title 标题
|
||||
* @param message 消息
|
||||
* @param callback 回调
|
||||
*/
|
||||
public static void showConfirmDialog(View view, int title, String message, MaterialDialog.SingleButtonCallback callback) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> new MaterialDialog.Builder(activity).title(title).content(message).positiveText(R.string.confirm).negativeText(R.string.cancel).onAny(callback).show());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示进度条
|
||||
* @param view context容器
|
||||
* @param title 标题
|
||||
* @param message 消息
|
||||
* @return 对话框引用
|
||||
*/
|
||||
public static AtomicReference<MaterialDialog> showProgressDialog(View view, int title, String message) {
|
||||
Activity activity = CommonLogic.getActivityFromView(view);
|
||||
AtomicReference<MaterialDialog> reference = new AtomicReference<>();
|
||||
if (activity != null && !activity.isFinishing()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(activity)
|
||||
.title(title)
|
||||
.content(message)
|
||||
.progress(false, 100, true)
|
||||
.cancelable(false)
|
||||
.show();
|
||||
reference.set(dialog);
|
||||
});
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,15 @@ import java.io.OutputStreamWriter;
|
|||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 文件工具类
|
||||
*/
|
||||
public class FileUtils {
|
||||
/**
|
||||
* 读取文本文件
|
||||
* @param file 文件
|
||||
* @return 文本
|
||||
*/
|
||||
public static String getFileText(File file) {
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
|
@ -34,6 +42,13 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取本地资源或Asset资源
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 输入流
|
||||
* @throws IOException 异常
|
||||
*/
|
||||
public static InputStream getLocalAsset(Context context, String filename) throws IOException {
|
||||
File file = new File(context.getFilesDir(), filename);
|
||||
if (file.exists()) {
|
||||
|
@ -42,6 +57,13 @@ public class FileUtils {
|
|||
return context.getAssets().open(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取JSON文件
|
||||
* @param file 文件
|
||||
* @param type 数据类型
|
||||
* @param <T> 泛型类型
|
||||
* @return 数据
|
||||
*/
|
||||
public static <T> T getFileJson(File file, TypeReference<T> type) {
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
|
@ -53,6 +75,13 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取JSON文件
|
||||
* @param file 文件
|
||||
* @param tClass 数据类型
|
||||
* @param <T> 泛型类型
|
||||
* @return 数据
|
||||
*/
|
||||
public static <T> T getFileJson(File file, Class<T> tClass) {
|
||||
try {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
|
@ -64,6 +93,12 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入JSON文件到本地
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @param content 内容
|
||||
*/
|
||||
public static void writeAssetJson(Context context, String filename, Object content) {
|
||||
try {
|
||||
String tmpFilename = filename + ".tmp";
|
||||
|
@ -78,6 +113,14 @@ public class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取JSON资源
|
||||
* @param context context
|
||||
* @param filename 资源名
|
||||
* @param tClass 数据类型
|
||||
* @param <T> 泛型类型
|
||||
* @return 数据
|
||||
*/
|
||||
public static <T> T getAssetJson(Context context, String filename, Class<T> tClass) {
|
||||
try {
|
||||
InputStream inputStream = getLocalAsset(context, filename);
|
||||
|
@ -89,6 +132,14 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取JSON资源
|
||||
* @param context context
|
||||
* @param filename 资源名
|
||||
* @param type 数据类型
|
||||
* @param <T> 泛型类型
|
||||
* @return 数据
|
||||
*/
|
||||
public static <T> T getAssetJson(Context context, String filename, TypeReference<T> type) {
|
||||
try {
|
||||
InputStream inputStream = getLocalAsset(context, filename);
|
||||
|
@ -100,6 +151,12 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取资源为字节数组
|
||||
* @param context context
|
||||
* @param filename 文件名
|
||||
* @return 字节数组
|
||||
*/
|
||||
public static byte[] getAssetBytes(Context context, String filename) {
|
||||
try {
|
||||
try (InputStream inputStream = getLocalAsset(context, filename)) {
|
||||
|
@ -110,10 +167,21 @@ public class FileUtils {
|
|||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化路径前缀
|
||||
* @param path 文件路径
|
||||
* @return 移除前缀后的路径
|
||||
*/
|
||||
public static String toPrettyPath(String path) {
|
||||
return StringUtils.removeStart(path, Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算资源文件SHA3-256
|
||||
* @param context context
|
||||
* @param filename 资源名
|
||||
* @return SHA3-256值
|
||||
*/
|
||||
public static String getFileHash(Context context, String filename) {
|
||||
try (InputStream inputStream = getLocalAsset(context, filename)) {
|
||||
return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString();
|
||||
|
@ -122,6 +190,11 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件SHA3-256
|
||||
* @param file 文件
|
||||
* @return SHA3-256值
|
||||
*/
|
||||
public static String getFileHash(File file) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return Hashing.sha256().hashBytes(ByteStreams.toByteArray(inputStream)).toString();
|
||||
|
|
|
@ -8,22 +8,48 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* JSON工具类
|
||||
*/
|
||||
public class JSONUtil {
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
static {
|
||||
// 允许未定义的属性
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
|
||||
// 允许尾部额外的逗号
|
||||
mapper.configure(JsonReadFeature.ALLOW_TRAILING_COMMA.mappedFeature(), true);
|
||||
// 允许数组设置空值
|
||||
mapper.configure(JsonReadFeature.ALLOW_MISSING_VALUES.mappedFeature(), true);
|
||||
// 允许Java注释
|
||||
mapper.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS.mappedFeature(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转JSON
|
||||
* @param object 数据
|
||||
* @return JSON字符串
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public static String toJson(Object object) throws Exception {
|
||||
return mapper.writeValueAsString(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验JSON
|
||||
* @param jsonString JSON字符串
|
||||
* @throws JsonProcessingException 异常
|
||||
*/
|
||||
public static void checkJson(String jsonString) throws JsonProcessingException {
|
||||
mapper.readValue(jsonString, Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
* @param jsonString JSON
|
||||
* @param cls 目标类型
|
||||
* @param <T> 泛型参数
|
||||
* @return 数据
|
||||
*/
|
||||
public static <T> T fromJson(String jsonString, Class<T> cls) {
|
||||
try {
|
||||
return mapper.readValue(jsonString, cls);
|
||||
|
@ -33,6 +59,13 @@ public class JSONUtil {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
* @param jsonString JSON
|
||||
* @param type 目标类型
|
||||
* @param <T> 泛型参数
|
||||
* @return 数据
|
||||
*/
|
||||
public static <T> T fromJson(String jsonString, TypeReference<T> type) {
|
||||
try {
|
||||
return mapper.readValue(jsonString, type);
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
package com.zane.smapiinstaller.utils;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 版本比较工具
|
||||
*/
|
||||
public class VersionUtil {
|
||||
/**
|
||||
* 比较单个版本段
|
||||
* @param sectionA sectionA
|
||||
* @param sectionB sectionB
|
||||
* @return 比较结果
|
||||
*/
|
||||
private static int compareVersionSection(String sectionA, String sectionB) {
|
||||
try {
|
||||
return Integer.compare(Integer.parseInt(sectionA), Integer.parseInt(sectionB));
|
||||
|
@ -40,6 +47,12 @@ public class VersionUtil {
|
|||
}
|
||||
return Integer.compare(listA.size(), listB.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空版本段
|
||||
* @param versionSections 版本段列表
|
||||
* @return 是否为空版本段
|
||||
*/
|
||||
private static boolean isZero(List<String> versionSections) {
|
||||
return !Iterables.filter(versionSections, version -> {
|
||||
try {
|
||||
|
@ -53,6 +66,12 @@ public class VersionUtil {
|
|||
}).iterator().hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个版本
|
||||
* @param versionA versionA
|
||||
* @param versionB versionB
|
||||
* @return 比较结果
|
||||
*/
|
||||
public static int compareVersion(String versionA, String versionB) {
|
||||
List<String> versionSectionsA = Splitter.on(".").splitToList(versionA);
|
||||
List<String> versionSectionsB = Splitter.on(".").splitToList(versionB);
|
||||
|
|
Loading…
Reference in New Issue