feat: 用 HDiffPatch 重写增量更新并内置常用平台二进制
- 移除 jbsdiff,改为调用 hdiffz/hpatchz,整包逐字节还原 - 按包大小分五档调参,全部使用流式模式控制内存(单进程峰值约 104MB) - 内置 windows64/linux64/linux_arm64/macos 二进制,其余平台可用 -Dttstd.hdiffpatch.dir 指定 - 新增 diff/patch/info/env 命令行
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<mainClass>com.ttstd.BsDiffTool</mainClass>
|
||||
<mainClass>com.ttstd.ApkDeltaUpdater</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
|
||||
9
pom.xml
9
pom.xml
@@ -13,13 +13,6 @@
|
||||
<artifactId>lunar</artifactId>
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
<!-- Java 版 bsdiff/bspatch(malensek/jbsdiff,BSD 协议),
|
||||
传递依赖 org.apache.commons:commons-compress -->
|
||||
<dependency>
|
||||
<groupId>io.sigpipe</groupId>
|
||||
<artifactId>jbsdiff</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<maven.compiler.source>25</maven.compiler.source>
|
||||
@@ -43,7 +36,7 @@
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.ttstd.BsDiffTool</mainClass>
|
||||
<mainClass>com.ttstd.ApkDeltaUpdater</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,172 +0,0 @@
|
||||
package com.ttstd;
|
||||
|
||||
import io.sigpipe.jbsdiff.InvalidHeaderException;
|
||||
import io.sigpipe.jbsdiff.ui.FileUI;
|
||||
import org.apache.commons.compress.compressors.CompressorException;
|
||||
import org.apache.commons.compress.compressors.CompressorStreamFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 基于 jbsdiff(io.sigpipe:jbsdiff)的命令行 bsdiff / bspatch 工具。
|
||||
*
|
||||
* <p>用法:</p>
|
||||
* <pre>
|
||||
* diff <oldFile> <newFile> <patchFile> [-c|--compressor bzip2|gz|pack200|xz]
|
||||
* patch <oldFile> <patchFile> <newFile>
|
||||
* </pre>
|
||||
*
|
||||
* <p>示例:</p>
|
||||
* <pre>
|
||||
* java -jar MavenUtils.jar diff old.bin new.bin patch.bin
|
||||
* java -jar MavenUtils.jar patch old.bin patch.bin restored.bin
|
||||
* </pre>
|
||||
*
|
||||
* <p>每次 bsdiff / bspatch 执行完成后都会输出耗时。</p>
|
||||
*/
|
||||
public class BsDiffTool {
|
||||
|
||||
private static final String DEFAULT_COMPRESSOR = CompressorStreamFactory.BZIP2;
|
||||
|
||||
private BsDiffTool() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
String command = args[0].toLowerCase(Locale.ROOT);
|
||||
try {
|
||||
switch (command) {
|
||||
case "bsdiff" -> diff(args);
|
||||
case "bspatch" -> patch(args);
|
||||
default -> printUsage();
|
||||
}
|
||||
} catch (CompressorException | InvalidHeaderException | IOException e) {
|
||||
System.err.println("Error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* bsdiff:由 oldFile 与 newFile 生成 patchFile。
|
||||
*/
|
||||
private static void diff(String[] args)
|
||||
throws CompressorException, InvalidHeaderException, IOException {
|
||||
if (args.length < 4) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
File oldFile = new File(args[1]);
|
||||
File newFile = new File(args[2]);
|
||||
File patchFile = new File(args[3]);
|
||||
String compression = parseCompressor(args);
|
||||
|
||||
requireExists(oldFile, "oldFile");
|
||||
requireExists(newFile, "newFile");
|
||||
|
||||
long startNanos = System.nanoTime();
|
||||
FileUI.diff(oldFile, newFile, patchFile, compression);
|
||||
|
||||
System.out.printf(
|
||||
"[bsdiff] %s (%d bytes) -> %s (%d bytes), compressor=%s%n",
|
||||
oldFile, oldFile.length(), newFile, newFile.length(), compression);
|
||||
System.out.printf("[bsdiff] patch 文件已生成: %s (%d bytes)%n",
|
||||
patchFile, patchFile.length());
|
||||
System.out.printf("[bsdiff] bsdiff 完成,耗时 %s%n",
|
||||
formatDuration(System.nanoTime() - startNanos));
|
||||
}
|
||||
|
||||
/**
|
||||
* bspatch:由 oldFile 与 patchFile 还原出 newFile(压缩算法自动识别)。
|
||||
*/
|
||||
private static void patch(String[] args)
|
||||
throws CompressorException, InvalidHeaderException, IOException {
|
||||
if (args.length < 4) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
File oldFile = new File(args[1]);
|
||||
File patchFile = new File(args[2]);
|
||||
File newFile = new File(args[3]);
|
||||
|
||||
requireExists(oldFile, "oldFile");
|
||||
requireExists(patchFile, "patchFile");
|
||||
|
||||
long startNanos = System.nanoTime();
|
||||
FileUI.patch(oldFile, newFile, patchFile);
|
||||
|
||||
System.out.printf(
|
||||
"[bspatch] %s (%d bytes) + %s (%d bytes) -> %s (%d bytes)%n",
|
||||
oldFile, oldFile.length(), patchFile, patchFile.length(),
|
||||
newFile, newFile.length());
|
||||
System.out.printf("[bspatch] bspatch 完成,耗时 %s%n",
|
||||
formatDuration(System.nanoTime() - startNanos));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从参数中解析压缩算法,支持 "-c xxx" / "--compressor xxx",默认 bzip2。
|
||||
*/
|
||||
private static String parseCompressor(String[] args) {
|
||||
for (int i = 4; i < args.length - 1; i++) {
|
||||
if ("-c".equals(args[i]) || "--compressor".equals(args[i])) {
|
||||
String name = args[i + 1].toLowerCase(Locale.ROOT);
|
||||
// CompressorStreamFactory 中 gzip 的别名为 "gz"
|
||||
return "gzip".equals(name) ? CompressorStreamFactory.GZIP : name;
|
||||
}
|
||||
}
|
||||
return DEFAULT_COMPRESSOR;
|
||||
}
|
||||
|
||||
private static void requireExists(File file, String name) {
|
||||
if (!file.isFile()) {
|
||||
System.err.printf("Error: %s 不存在或不是文件: %s%n", name, file);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将时长(纳秒)转换为可读字符串:
|
||||
* 不足 1 秒用 ms,不足 1 分钟用 s,不足 1 小时用 min,否则用 h。
|
||||
*/
|
||||
private static String formatDuration(long nanos) {
|
||||
double ms = nanos / 1_000_000.0;
|
||||
if (ms < 1_000.0) {
|
||||
return String.format(Locale.ROOT, "%.3f ms", ms);
|
||||
}
|
||||
double totalSeconds = ms / 1_000.0;
|
||||
if (totalSeconds < 60.0) {
|
||||
return String.format(Locale.ROOT, "%.3f s", totalSeconds);
|
||||
}
|
||||
long minutes = (long) (totalSeconds / 60.0);
|
||||
if (minutes < 60) {
|
||||
return String.format(Locale.ROOT, "%d min %.3f s", minutes, totalSeconds - minutes * 60.0);
|
||||
}
|
||||
long hours = minutes / 60;
|
||||
return String.format(Locale.ROOT, "%d h %d min %.3f s",
|
||||
hours, minutes % 60, totalSeconds - minutes * 60.0);
|
||||
}
|
||||
|
||||
private static void printUsage() {
|
||||
System.out.println("""
|
||||
用法:
|
||||
diff <oldFile> <newFile> <patchFile> [-c|--compressor bzip2|gz|pack200|xz]
|
||||
patch <oldFile> <patchFile> <newFile>
|
||||
|
||||
示例:
|
||||
java -jar MavenUtils.jar diff old.bin new.bin patch.bin
|
||||
java -jar MavenUtils.jar patch old.bin patch.bin restored.bin
|
||||
|
||||
说明:
|
||||
diff 默认使用 bzip2 压缩(与 bsdiff 参考实现兼容),
|
||||
可用 --compressor 指定 gz / pack200 / xz;
|
||||
patch 会自动识别补丁所用的压缩算法。""");
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/hdiffpatch/hdiffpatch_v5.1.3_bin_linux64.zip
Normal file
BIN
src/main/resources/hdiffpatch/hdiffpatch_v5.1.3_bin_linux64.zip
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/hdiffpatch/hdiffpatch_v5.1.3_bin_macos.zip
Normal file
BIN
src/main/resources/hdiffpatch/hdiffpatch_v5.1.3_bin_macos.zip
Normal file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user