package com.taobao.android.tpatch.builder;
import com.android.utils.ILogger;
import com.taobao.android.BasePatchTool;
import com.taobao.android.TPatchDexTool;
import com.taobao.android.differ.dex.PatchException;
import com.taobao.android.object.BuildPatchInfos;
import com.taobao.android.object.PatchBundleInfo;
import com.taobao.android.object.PatchInfo;
import com.taobao.android.task.ExecutorServicesHelper;
import com.taobao.android.tpatch.model.BundleBO;
import com.taobao.android.tpatch.utils.JarSplitUtils;
import com.taobao.android.tpatch.utils.MD5Util;
import com.taobao.android.tpatch.utils.PathUtils;
import com.taobao.android.utils.ZipUtils;
import com.taobao.common.dexpatcher.DexPatchApplier;
import com.taobao.common.dexpatcher.DexPatchGenerator;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Date;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
public class PatchFileBuilder {
private static final String ROLLBACK_VERSION = "-1";
private final BuildPatchInfos historyBuildPatchInfos;
private final PatchInfo currentBuildPatchInfo;
private final File currentPatchFile;
private final String baseVersion;
private final File tPatchTmpFolder;
private Map<String, File> awbMaps;
private Map<String, PatchInfo> hisPatchInfos = new HashMap<String, PatchInfo>();
private List<String>versionList = new ArrayList<>();
private final File patchsFolder;
private List<String> noPatchBundles;
public PatchFileBuilder(BuildPatchInfos historyBuildPatchInfos, File currentPatchFile,
PatchInfo currentBuildPatchInfo, Map<String, File> awbMaps, File targetFolder,
String baseVersion) {
if (null == historyBuildPatchInfos) {
this.historyBuildPatchInfos = new BuildPatchInfos();
} else {
this.historyBuildPatchInfos = historyBuildPatchInfos;
}
this.currentBuildPatchInfo = currentBuildPatchInfo;
this.awbMaps = awbMaps;
this.currentPatchFile = currentPatchFile;
this.baseVersion = baseVersion;
this.tPatchTmpFolder = new File(targetFolder.getParentFile(), "tpatch-tmp");
this.patchsFolder = targetFolder;
}
public void setNoPatchBundles(List<String> noPatchBundles) {
this.noPatchBundles = noPatchBundles;
}
/**
* 创建历史版本的tpatch
*/
public BuildPatchInfos createHistoryTPatches(boolean diffBundleDex, final ILogger logger) throws PatchException {
final BuildPatchInfos buildPatchInfos = new BuildPatchInfos();
List<PatchInfo> patchInfos = historyBuildPatchInfos.getPatches();
String taskName = "CreateHisPatch";
ExecutorServicesHelper executorServicesHelper = new ExecutorServicesHelper();
for (final PatchInfo patchInfo : patchInfos) {
if (!versionList.isEmpty()&&!versionList.contains(patchInfo.getPatchVersion())){
continue;
}
executorServicesHelper.submitTask(taskName, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
if (null != logger) {
logger.info("[CreateHisPatch]" + patchInfo.getPatchVersion() + "....");
}
try {
hisPatchInfos.put(patchInfo.getPatchVersion(), patchInfo);
PatchInfo newPatchInfo = createHisTPatch(patchInfo.getPatchVersion(), logger);
buildPatchInfos.getPatches().add(newPatchInfo);
} catch (IOException e) {
throw new PatchException(e.getMessage(), e);
}
return true;
}
});
}
try {
executorServicesHelper.waitTaskCompleted(taskName);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
executorServicesHelper.stop();
return buildPatchInfos;
}
// 获取主bundle
private PatchBundleInfo getMainBundleInfo(PatchInfo patchInfo) {
for (PatchBundleInfo bundleInfo : patchInfo.getBundles()) {
if (bundleInfo.getMainBundle()) {
return bundleInfo;
}
}
return null;
}
/**
* 创建指定版本的patch文件
*
* @param targetVersion
*/
private PatchInfo createHisTPatch(String targetVersion, ILogger logger) throws IOException, PatchException {
PatchInfo hisPatchInfo = hisPatchInfos.get(targetVersion);
String patchName = "patch-" + currentBuildPatchInfo.getPatchVersion() + "@" + hisPatchInfo.getPatchVersion();
File destTPathTmpFolder = new File(tPatchTmpFolder, patchName);
destTPathTmpFolder.mkdirs();
File curTPatchUnzipFolder = unzipCurTPatchFolder(patchName);
// 处理awb的更新
List<BundlePatch> bundlePatches = diffPatch(hisPatchInfo, currentBuildPatchInfo);
PatchInfo newPatchInfo = processBundlePatch(hisPatchInfo, bundlePatches,curTPatchUnzipFolder);
// 比对主bundle的信息
PatchBundleInfo curMainBundleInfo = getMainBundleInfo(currentBuildPatchInfo);
PatchBundleInfo mainBundleInfo = new PatchBundleInfo();
mainBundleInfo.setMainBundle(true);
mainBundleInfo.setNewBundle(false);
if (null != curMainBundleInfo) {
File mainBundleFile = new File(curTPatchUnzipFolder, curMainBundleInfo.getName() + ".so");
FileUtils.copyFileToDirectory(mainBundleFile, destTPathTmpFolder);
mainBundleInfo.setVersion(curMainBundleInfo.getVersion());
mainBundleInfo.setPkgName(curMainBundleInfo.getPkgName());
mainBundleInfo.setBaseVersion(curMainBundleInfo.getBaseVersion());
mainBundleInfo.setName(curMainBundleInfo.getName());
mainBundleInfo.setPkgName(curMainBundleInfo.getPkgName());
mainBundleInfo.setApplicationName(curMainBundleInfo.getApplicationName());
newPatchInfo.getBundles().add(mainBundleInfo);
}
// 生成tpatch文件
for (PatchBundleInfo bundleInfo : newPatchInfo.getBundles()) {
if (bundleInfo.getMainBundle() || bundleInfo.getNewBundle() || noPatchBundles.contains(bundleInfo.getPkgName())) {
File bundleFolder = new File(destTPathTmpFolder, bundleInfo.getName());
File soFile = new File(destTPathTmpFolder, bundleInfo.getName() + ".so");
if (soFile.exists() || bundleInfo.getVersion().equals(ROLLBACK_VERSION)) {
continue;
}
zipBunldeSo(bundleFolder, soFile);
FileUtils.deleteDirectory(bundleFolder);
}
}
File tPatchFile = new File(patchsFolder, newPatchInfo.getFileName());
if (tPatchFile.exists()) FileUtils.deleteQuietly(tPatchFile);
zipBunldeSo(destTPathTmpFolder, tPatchFile);
if (null != logger) {
logger.info("[TPatchFile]" + tPatchFile.getAbsolutePath());
}
return newPatchInfo;
}
/**
* 生成主dex的so
*
* @param bundleFolder
* @param soOutputFile
*/
private void zipBunldeSo(File bundleFolder, File soOutputFile) throws PatchException {
try {
Manifest manifest = createManifest();
FileOutputStream fileOutputStream = new FileOutputStream(soOutputFile);
JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fileOutputStream), manifest);
// Add ZIP entry to output stream.
// jos.setComment(patchVersion+"@"+targetVersion);
File[] files = bundleFolder.listFiles();
for (File file : files) {
if (file.isDirectory()) {
addDirectory(jos, file, file.getName());
} else {
addFile(jos, file);
}
}
IOUtils.closeQuietly(jos);
if (null != fileOutputStream) IOUtils.closeQuietly(fileOutputStream);
} catch (IOException e) {
throw new PatchException(e.getMessage(), e);
}
}
/**
* 比较2个patch版本之间的差异
*
* @param hisPatchInfo
* @param currentPatchInfo
* @return
*/
private List<BundlePatch> diffPatch(PatchInfo hisPatchInfo, PatchInfo currentPatchInfo) {
List<BundlePatch> list = new LinkedList<BundlePatch>();
Map<String, PatchBundleInfo> hisBundles = toMap(hisPatchInfo.getBundles());
Map<String, PatchBundleInfo> curBundles = toMap(currentPatchInfo.getBundles());
for (Map.Entry<String, PatchBundleInfo> entry : curBundles.entrySet()) {
String bundleName = entry.getKey();
PatchBundleInfo curBundleInfo = entry.getValue();
BundlePatch bundlePatch = new BundlePatch();
bundlePatch.name = curBundleInfo.getName();
bundlePatch.dependency = curBundleInfo.getDependency();
bundlePatch.pkgName = curBundleInfo.getPkgName();
bundlePatch.artifactId = curBundleInfo.getArtifactId();
bundlePatch.version = curBundleInfo.getVersion();
bundlePatch.newBundle = curBundleInfo.getNewBundle();
bundlePatch.hisPatchUrl = hisPatchInfo.getDownloadUrl();
bundlePatch.mainBundle = curBundleInfo.getMainBundle();
bundlePatch.baseVersion = curBundleInfo.getBaseVersion();
if (hisBundles.containsKey(bundleName)&&!hisBundles.get(bundleName).getNewBundle()) { // 如果之前的patch版本也包含这个bundle的patch
PatchBundleInfo hisBundleInfo = hisBundles.get(bundleName);
bundlePatch.baseVersion = hisBundleInfo.getVersion();
if (curBundleInfo.getVersion().equalsIgnoreCase(hisBundleInfo.getVersion())) { // 如果2个patch版本没变化
// 说明:为了防止虽然版本号没变化但是文件内容也有变化的情况,直接也做merge操作
bundlePatch.bundlePolicy = BundlePolicy.MERGE;
} else {// 如果版本有变化,进行merge操作
bundlePatch.bundlePolicy = BundlePolicy.MERGE;
}
hisBundles.remove(bundleName);
} else { // 如果历史的patch中没有包含该bundle
bundlePatch.bundlePolicy = BundlePolicy.ADD;
}
list.add(bundlePatch);
}
// 继续添加在历史版本里有变动的,在新的版本进行回滚的操作
for (Map.Entry<String, PatchBundleInfo> entry : hisBundles.entrySet()) {
PatchBundleInfo hisBundleInfo = entry.getValue();
BundlePatch bundlePatch = new BundlePatch();
bundlePatch.name = hisBundleInfo.getName();
bundlePatch.dependency = hisBundleInfo.getDependency();
bundlePatch.pkgName = hisBundleInfo.getPkgName();
bundlePatch.artifactId = hisBundleInfo.getArtifactId();
bundlePatch.bundlePolicy = BundlePolicy.ROLLBACK;
bundlePatch.version = ROLLBACK_VERSION;
bundlePatch.baseVersion = hisBundleInfo.getVersion();
list.add(bundlePatch);
}
return list;
}
/**
* 解压当前的tpatch文件
*/
private File unzipCurTPatchFolder(String patchName) throws IOException {
File curTPatchUnzipFolder = new File(currentPatchFile.getParentFile(), "curTpatch");
synchronized (this) {
if (curTPatchUnzipFolder.exists() && curTPatchUnzipFolder.isDirectory()) {
return curTPatchUnzipFolder;
}
curTPatchUnzipFolder.mkdirs();
ZipUtils.unzip(currentPatchFile, curTPatchUnzipFolder.getAbsolutePath());
File[] libs = curTPatchUnzipFolder.listFiles();
if (libs != null && libs.length > 0) {
for (File lib : libs) {
if (lib.isFile() && lib.getName().endsWith(".so")) {
File destFolder = new File(lib.getParentFile(), FilenameUtils.getBaseName(lib.getName()));
System.out.println(lib.getAbsolutePath());
ZipUtils.unzip(lib, destFolder.getAbsolutePath());
}
}
}
}
return curTPatchUnzipFolder;
}
/**
* 处理各自bundle的patch文件
*
* @param hisPatchInfo
* @param bundlePatchs
*/
private PatchInfo processBundlePatch(PatchInfo hisPatchInfo, List<BundlePatch> bundlePatchs,File curTPatchUnzipFolder) throws IOException,
PatchException {
String patchName = "patch-" + currentBuildPatchInfo.getPatchVersion() + "@" + hisPatchInfo.getPatchVersion();
PatchInfo patchInfo = new PatchInfo();
patchInfo.setFileName(patchName + ".tpatch");
patchInfo.setTargetVersion(hisPatchInfo.getPatchVersion());
patchInfo.setPatchVersion(currentBuildPatchInfo.getPatchVersion());
File hisTPatchFile = new File(tPatchTmpFolder, hisPatchInfo.getPatchVersion() + "-download.tpatch");
File hisTPatchUnzipFolder = new File(tPatchTmpFolder, hisPatchInfo.getPatchVersion());
File destTPathTmpFolder = new File(tPatchTmpFolder, patchName);
if (!destTPathTmpFolder.exists()) {
destTPathTmpFolder.mkdirs();
}
for (BundlePatch bundlePatch : bundlePatchs) {
boolean addToPatch = true;
String bundleName = "lib" + bundlePatch.pkgName.replace('.', '_');
if (bundlePatch.mainBundle) {
continue;
} else if (noPatchBundles.contains(bundlePatch.pkgName)) {
File currentBundle = new File(curTPatchUnzipFolder, "lib" + bundlePatch.pkgName.replace(".", "_") + ".so");
if (!currentBundle.exists()) {
continue;
}
FileUtils.copyFileToDirectory(currentBundle, destTPathTmpFolder);
PatchBundleInfo patchBundleInfo = new PatchBundleInfo();
patchBundleInfo.setApplicationName(bundlePatch.applicationName);
patchBundleInfo.setArtifactId(bundlePatch.artifactId);
patchBundleInfo.setMainBundle(false);
patchBundleInfo.setNewBundle(bundlePatch.newBundle);
patchBundleInfo.setName(bundleName);
patchBundleInfo.setPkgName(bundlePatch.pkgName);
patchBundleInfo.setVersion(bundlePatch.version);
patchBundleInfo.setBaseVersion(bundlePatch.baseVersion);
patchBundleInfo.setDependency(bundlePatch.dependency);
patchInfo.getBundles().add(patchBundleInfo);
continue;
}
File curBundleFolder = new File(curTPatchUnzipFolder, bundleName);
File bundleDestFolder = new File(destTPathTmpFolder, bundleName);
PatchBundleInfo patchBundleInfo = new PatchBundleInfo();
patchBundleInfo.setApplicationName(bundlePatch.applicationName);
patchBundleInfo.setArtifactId(bundlePatch.artifactId);
patchBundleInfo.setMainBundle(false);
patchBundleInfo.setNewBundle(bundlePatch.newBundle);
patchBundleInfo.setName(bundleName);
patchBundleInfo.setPkgName(bundlePatch.pkgName);
patchBundleInfo.setVersion(bundlePatch.version);
patchBundleInfo.setBaseVersion(bundlePatch.baseVersion);
patchBundleInfo.setDependency(bundlePatch.dependency);
switch (bundlePatch.bundlePolicy) {
case ADD:
bundleDestFolder.mkdirs();
FileUtils.copyDirectory(curBundleFolder, bundleDestFolder);
break;
case REMOVE:
addToPatch = false;
break; // donothing
case ROLLBACK:// donothing
break;
case MERGE:
downloadTPathAndUnzip(hisPatchInfo.getDownloadUrl(), hisTPatchFile, hisTPatchUnzipFolder);
File hisBundleFolder = new File(hisTPatchUnzipFolder, bundleName);
if (!hisBundleFolder.exists()) { //如果历史的文件不存在,就直接覆盖
throw new PatchException("The bundle:" + bundleName + " does not existed in tpatch:"
+ hisPatchInfo.getDownloadUrl());
// bundleDestFolder.mkdirs();
// FileUtils.copyDirectory(curBundleFolder, bundleDestFolder);
} else {
File fullAwbFile = awbMaps.get(bundlePatch.artifactId);
if (fullAwbFile == null){
System.out.println(bundlePatch.artifactId + " is not exits!");
FileUtils.copyDirectory(curBundleFolder, bundleDestFolder);
break;
}
copyDiffFiles(fullAwbFile, curBundleFolder, hisBundleFolder, bundleDestFolder);
if (!bundleDestFolder.exists()||bundleDestFolder.listFiles().length == 0){
addToPatch = false;
}
}
break;
}
if (addToPatch) {
patchInfo.getBundles().add(patchBundleInfo);
}
}
return patchInfo;
}
/**
* 复制拷贝有diff的文件
* @param fullLibFile
* @param curBundleFolder
* @param hisBundleFolder
* @param destBundleFolder
* @param bundleName
*/
private void copyDiffFiles(File fullLibFile, File curBundleFolder, File hisBundleFolder,
File destBundleFolder) throws IOException, PatchException {
Map<String, FileDef> curBundleFileMap = getListFileMap(curBundleFolder);
Map<String, FileDef> hisBundleFileMap = getListFileMap(hisBundleFolder);
Set<String> rollbackFiles = new HashSet<String>();
// 判断差别
for (Map.Entry<String, FileDef> entry : curBundleFileMap.entrySet()) {
String curFilePath = entry.getKey();
FileDef curFileDef = entry.getValue();
File destFile = new File(destBundleFolder, curFilePath);
// if (curFilePath.endsWith(".dex")){
// createHisBundleDex(curFileDef,hisBundleFileMap.get(curFilePath),destFile,fullLibFile);
// hisBundleFileMap.remove(curFilePath);
// continue;
// }
if (hisBundleFileMap.containsKey(curFilePath)) {
FileDef hisFileDef = hisBundleFileMap.get(curFilePath);
if (curFileDef.md5.equals(hisFileDef.md5)) {
// donothing
} else {
FileUtils.copyFile(curFileDef.file, destFile);
}
hisBundleFileMap.remove(curFilePath);
} else { // 如果新的patch里有这个文件
FileUtils.copyFile(curFileDef.file, destFile);
}
}
for (Map.Entry<String, FileDef> entry : hisBundleFileMap.entrySet()) {
rollbackFiles.add(entry.getKey());
}
if (rollbackFiles.size() > 0) {
JarSplitUtils.splitZipToFolder(fullLibFile, destBundleFolder, rollbackFiles);
}
}
private void createHisBundleDex(FileDef curFileDef, FileDef fileDef, File destFile, File newBundleFile) throws IOException {
if (fileDef == null){
FileUtils.copyFile(curFileDef.file,destFile);
}else {
DexPatchApplier dexPatchApplier = new DexPatchApplier(getBaseDexFile(newBundleFile,true),fileDef.file);
File tempFile = new File(destFile.getParentFile(),"merge.dex");
dexPatchApplier.executeAndSaveTo(tempFile);
DexPatchGenerator dexPatchGenerator = new DexPatchGenerator(tempFile,getBaseDexFile(newBundleFile,false));
dexPatchGenerator.executeAndSaveTo(destFile);
FileUtils.deleteQuietly(tempFile);
}
}
private File getBaseDexFile(File newBundleFile,boolean base) {
File newApkUnzipFolder = new File(newBundleFile.getAbsolutePath().split("lib/armeabi")[0]);
File baseApkUnzipFolder = new File(newApkUnzipFolder.getParentFile(), BasePatchTool.BASE_APK_UNZIP_NAME);
File baseBundleFile = null;
File oldBundleFolder = null;
if (base) {
baseBundleFile = new File(baseApkUnzipFolder, "lib" + File.separator + "armeabi" + File.separator + newBundleFile.getName());
oldBundleFolder = new File(baseBundleFile.getParentFile(), FilenameUtils.getBaseName(baseBundleFile.getName()));
System.out.println("getBaseDexFile:" + new File(oldBundleFolder, "classes.dex").getAbsolutePath());
return new File(oldBundleFolder, "classes.dex");
}else {
baseBundleFile = new File(newApkUnzipFolder, "lib" + File.separator + "armeabi" + File.separator + newBundleFile.getName());
oldBundleFolder = new File(baseBundleFile.getParentFile(), FilenameUtils.getBaseName(baseBundleFile.getName()));
System.out.println("getNewDexFile:" + new File(oldBundleFolder, "classes.dex").getAbsolutePath());
return new File(oldBundleFolder, "classes.dex");
}
}
/**
* 将指定文件夹下的文件转换为map
*
* @param folder
* @return
* @throws PatchException
*/
private Map<String, FileDef> getListFileMap(File folder) throws PatchException, IOException {
Map<String, FileDef> map = new HashMap<String, FileDef>();
if (!folder.exists() || !folder.isDirectory()) {
throw new PatchException("The input folder:" + folder.getAbsolutePath()
+ " does not existed or is not a directory!");
}
Collection<File> files = FileUtils.listFiles(folder, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
for (File file : files) {
String path = PathUtils.toRelative(folder, file.getAbsolutePath());
String md5 = MD5Util.getFileMD5String(file);
FileDef fileDef = new FileDef(md5, path, file);
map.put(path, fileDef);
}
return map;
}
public void setHistroyVersionList(List<String> versionList) {
this.versionList = versionList;
}
// 简易文件定义
class FileDef {
String md5;
String relativePath;
File file;
public FileDef(String md5, String relativePath, File file) {
this.md5 = md5;
this.relativePath = relativePath;
this.file = file;
}
}
/**
* 如果下载的文件不存在,则下载文件
*
* @param httpUrl
* @param saveFile
* @param tmpUnzipFolder
* @throws IOException
*/
private void downloadTPathAndUnzip(String httpUrl, File saveFile, File tmpUnzipFolder) throws IOException {
if (!saveFile.exists() || !saveFile.isFile()) {
downloadFile(httpUrl, saveFile);
}
ZipUtils.unzip(saveFile, tmpUnzipFolder.getAbsolutePath());
}
/**
* http下载
*/
private void downloadFile(String httpUrl, File saveFile) throws IOException {
// 下载网络文件
int bytesum = 0;
int byteread = 0;
URL url = new URL(httpUrl);
URLConnection conn = url.openConnection();
InputStream inStream = conn.getInputStream();
FileOutputStream fs = new FileOutputStream(saveFile);
byte[] buffer = new byte[1204];
while ((byteread = inStream.read(buffer)) != -1) {
bytesum += byteread;
fs.write(buffer, 0, byteread);
}
fs.flush();
inStream.close();
fs.close();
}
/**
* 转换为map
*
* @param bundles
* @return
*/
private Map<String, PatchBundleInfo> toMap(List<PatchBundleInfo> bundles) {
Map<String, PatchBundleInfo> bundleMap = new HashMap<String, PatchBundleInfo>();
for (PatchBundleInfo bundle : bundles) {
bundleMap.put(bundle.getArtifactId(), bundle);
}
return bundleMap;
}
/**
* 创建Andfix的manifest信息
*
* @return
*/
private Manifest createManifest() {
Manifest manifest = new Manifest();
Attributes main = manifest.getMainAttributes();
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (JarPatch)");
main.putValue("Created-Time", new Date(System.currentTimeMillis()).toGMTString());
return manifest;
}
/**
* 往jar文件里增加文件
*
* @param jos
* @param file
*/
private void addFile(JarOutputStream jos, File file) throws PatchException {
byte[] buf = new byte[8064];
String path = file.getName();
InputStream in = null;
try {
in = new FileInputStream(file);
ZipEntry fileEntry = new ZipEntry(path);
jos.putNextEntry(fileEntry);
// Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
jos.write(buf, 0, len);
}
// Complete the entry
jos.closeEntry();
in.close();
} catch (IOException e) {
throw new PatchException(e.getMessage(), e);
}
}
/**
* Adds a directory to a with a directory prefix.
*
* @param jos ZipArchiver to use to archive the file.
* @param directory The directory to add.
* @param prefix An optional prefix for where in the Jar file the directory's contents should go.
*/
protected void addDirectory(JarOutputStream jos, File directory, String prefix) throws PatchException {
if (directory != null && directory.exists()) {
Collection<File> files = FileUtils.listFiles(directory, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
byte[] buf = new byte[8064];
for (File file : files) {
if (file.isDirectory()) {
continue;
}
String path = prefix + File.separator + PathUtils.toRelative(directory, file.getAbsolutePath());
InputStream in = null;
try {
in = new FileInputStream(file);
ZipEntry fileEntry = new ZipEntry(path);
jos.putNextEntry(fileEntry);
// Transfer bytes from the file to the ZIP file
int len;
while ((len = in.read(buf)) > 0) {
jos.write(buf, 0, len);
}
// Complete the entry
jos.closeEntry();
in.close();
} catch (IOException e) {
throw new PatchException(e.getMessage(), e);
}
}
}
}
class BundlePatch {
String pkgName;
String applicationName;
List<String> dependency;
String name;
String artifactId;
boolean newBundle;
BundlePolicy bundlePolicy;
String version;
String hisPatchUrl;
boolean mainBundle;
String baseVersion;
}
/**
* 表示当前patch的bundle与之前patch版本的策略,包括合并,回滚,无变化
*/
enum BundlePolicy {
ADD, MERGE, REMOVE, ROLLBACK;
}
}