/*
* Created by LuaView.
* Copyright (c) 2017, Alibaba Group. All rights reserved.
*
* This source code is licensed under the MIT.
* For the full copyright and license information,please view the LICENSE file in the root directory of this source tree.
*/
package com.taobao.luaview.scriptbundle.asynctask.delegate;
import android.content.Context;
import android.content.res.AssetManager;
import com.taobao.luaview.scriptbundle.LuaScriptManager;
import com.taobao.luaview.scriptbundle.ScriptBundle;
import com.taobao.luaview.scriptbundle.ScriptFile;
import com.taobao.luaview.scriptbundle.asynctask.SimpleTask;
import com.taobao.luaview.scriptbundle.asynctask.SimpleTask1;
import com.taobao.luaview.util.AssetUtil;
import com.taobao.luaview.util.DebugUtil;
import com.taobao.luaview.util.FileUtil;
import com.taobao.luaview.util.IOUtil;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* ScriptBundle Unpack Delegate
*
* @author song
* @date 17/2/8
* 主要功能描述
* 修改描述
* 下午2:43 song XXX
*/
public class ScriptBundleUnpackDelegate {
/**
* 将所有assert下面的脚本解压缩到文件系统
*
* @param assetFolderPath
*/
public static void unpackAllAssetScripts(final Context context, final String assetFolderPath) {
if (context != null && assetFolderPath != null) {
new SimpleTask1<Object>() {//起simple task 来解压包
@Override
protected Object doInBackground(Object[] params) {
final AssetManager assetManager = context.getAssets();
if (assetManager != null) {
try {
final String[] assetBundles = assetManager.list(assetFolderPath);//list 耗时
if (assetBundles != null) {
for (final String assetBundleFileName : assetBundles) {
if (LuaScriptManager.isLuaScriptZip(assetBundleFileName)) {//如果是luaview zip,则解包
unpack(context, FileUtil.removePostfix(assetBundleFileName), assetFolderPath + File.separator + assetBundleFileName);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}.executeInPool();
}
}
/**
* unpack asset
*
* @param url
* @param assetFilePath
* @return
*/
public static ScriptBundle unpack(Context context, String url, String assetFilePath) {
final String scriptBundleFilePath = LuaScriptManager.buildScriptBundleFilePath(url);
final InputStream inputStream = assetFilePath != null ? AssetUtil.open(context, assetFilePath) : FileUtil.open(scriptBundleFilePath);//额外参数,告知了inputstream (asset的情况)
try {
return unpackBundle(LuaScriptManager.isLuaBytecodeUrl(url), url, inputStream);//TODO 性能瓶颈
} catch (IOException e) {
return null;
} finally {
if (assetFilePath != null) {//asset,copy原始文件到文件夹下
FileUtil.copy(AssetUtil.open(context, assetFilePath), scriptBundleFilePath);
}
}
}
/**
* unpack asset
*
* @param url
* @return
*/
public static ScriptBundle unpack(String url) {
final String scriptBundleFilePath = LuaScriptManager.buildScriptBundleFilePath(url);
final InputStream inputStream = FileUtil.open(scriptBundleFilePath);//额外参数,告知了inputstream (asset的情况)
try {
return unpackBundle(LuaScriptManager.isLuaBytecodeUrl(url), url, inputStream);//TODO 性能瓶颈
} catch (IOException e) {
return null;
}
}
/**
* unpack asset
*
* @param url
* @return
*/
public static ScriptBundle unpack(String url, byte[] fileData) {
try {
return unpackBundle(LuaScriptManager.isLuaBytecodeUrl(url), url, new ByteArrayInputStream(fileData));//TODO 性能瓶颈
} catch (IOException e) {
return null;
}
}
/**
* unpack asset
*
* @param url
* @return
*/
public static ScriptBundle unpack(String url, InputStream inputStream) {
try {
return unpackBundle(LuaScriptManager.isLuaBytecodeUrl(url), url, inputStream);//TODO 性能瓶颈
} catch (IOException e) {
return null;
}
}
/**
* 加载指定目录下的所有lua文件
*
* @param destFilePath path or file
* @return
*/
public static ScriptBundle loadBundle(boolean isBytecode, final String url, String destFilePath) {
String rawFilePath = LuaScriptManager.buildScriptBundleFilePath(url);
File file = new File(rawFilePath);
if (!file.exists()) {
return null;
}
if (file.isFile()) {
try {
return unpackBundleRaw(isBytecode, url, file);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* unpack a bundle
*
* @param inputStream
* @param url
* @return
*/
public static ScriptBundle unpackBundle(final boolean isBytecode, final String url, final InputStream inputStream) throws IOException {
if (inputStream == null || url == null) {
return null;
}
final ScriptBundle scriptBundle = new ScriptBundle();
final ZipInputStream zipStream = new ZipInputStream(new BufferedInputStream(inputStream));
final String scriptBundleFolderPath = LuaScriptManager.buildScriptBundleFolderPath(url);
final Map<String, byte[]> luaSigns = new HashMap<String, byte[]>();
final Map<String, byte[]> luaRes = new HashMap<String, byte[]>();
final Map<String, byte[]> luaScripts = new HashMap<String, byte[]>();
scriptBundle.setUrl(url);
scriptBundle.setBytecode(isBytecode);
scriptBundle.setBaseFilePath(scriptBundleFolderPath);
ZipEntry entry;
String rawName = null;
String fileName = null;
String filePath = null;
DebugUtil.tsi("luaviewp-unpackBundle-zip");
while ((entry = zipStream.getNextEntry()) != null) {
// 处理../ 这种方式只能使用单层路径,不能处理子目录,在这里可以添加公用path
rawName = entry.getName();
if (rawName == null || rawName.indexOf("../") != -1) {
zipStream.close();
return null;
}
fileName = FileUtil.getSecurityFileName(rawName);
if (entry.isDirectory()) {
filePath = FileUtil.buildPath(scriptBundleFolderPath, fileName);
File dir = new File(filePath);
if (!dir.exists()) {
dir.mkdir();
}
} else {
byte[] fileData = IOUtil.toBytes(zipStream);//TODO 性能瓶颈
if (LuaScriptManager.isLuaEncryptScript(fileName)) {//lua file (source or prototype)
scriptBundle.addScript(new ScriptFile(url, scriptBundleFolderPath, fileName, fileData, null));
luaScripts.put(fileName, fileData);
} else if (LuaScriptManager.isLuaSignFile(fileName)) {//签名文件,不解压到文件系统
luaSigns.put(fileName, fileData);
} else {//其他文件解压到文件系统(图片、资源)
filePath = FileUtil.buildPath(scriptBundleFolderPath, fileName);
luaRes.put(filePath, fileData);
}
}
//close entry
zipStream.closeEntry();
}
DebugUtil.tei("luaviewp-unpackBundle-zip");
zipStream.close();
ScriptFile scriptFile = null;
Map<String, ScriptFile> fileMap = scriptBundle.getScriptFileMap();
for (String key : fileMap.keySet()) {
scriptFile = fileMap.get(key);
scriptFile.signData = luaSigns.get(scriptFile.signFileName);
}
if (luaRes.size() > 0) {
new SimpleTask<Map<String, byte[]>>() {//写资源文件
@Override
public void doTask(Map<String, byte[]>... params) {
if (params != null && params.length > 0) {
Map<String, byte[]> fileToBeSave = params[0];
for (Map.Entry<String, byte[]> file : fileToBeSave.entrySet()) {//save all res files
FileUtil.save(file.getKey(), file.getValue());
}
}
}
}.executeInPool(luaRes);
}
if (luaScripts.size() > 0) {
new SimpleTask<Object>() {//写xxx.lvbundle文件
@Override
public void doTask(Object... params) {
boolean isBytecode = params != null && params.length >= 0 ? (Boolean) params[0] : false;
Map<String, byte[]> fileData = params != null && params.length >= 1 ? (Map<String, byte[]>) params[1] : null;
Map<String, byte[]> signData = params != null && params.length >= 2 ? (Map<String, byte[]>) params[2] : null;
saveFilesUsingOutputStream(isBytecode, url, fileData, signData);
}
}.executeInPool(isBytecode, luaScripts, luaSigns);
}
return scriptBundle;
}
/**
* save files as xxx.lvbundle
*
* @param url
* @param fileDatas
*/
private static void saveFilesUsingOutputStream(boolean isBytecode, String url, Map<String, byte[]> fileDatas, Map<String, byte[]> signDatas) {
String filePath = LuaScriptManager.buildScriptBundleFilePath(url);
File tmpFile = FileUtil.createFile(filePath);
DataOutputStream output = null;
try {
output = new DataOutputStream(new FileOutputStream(tmpFile));
output.writeInt(fileDatas.size());
String fileNameStr = null;
for (Map.Entry<String, byte[]> file : fileDatas.entrySet()) {
fileNameStr = file.getKey();
byte[] fileName = fileNameStr.getBytes();
output.writeInt(fileName.length);
output.write(fileName);
byte[] fileData = file.getValue();
output.writeInt(fileData.length);
output.write(fileData);
if (!isBytecode) {//非bytecode还写sign
byte[] signData = signDatas.get(LuaScriptManager.changeSuffix(fileNameStr, LuaScriptManager.POSTFIX_LV) + LuaScriptManager.POSTFIX_SIGN);//sign file name, TODO 这里可以优化一下,名称使用统一的名称
output.writeInt(signData.length);
output.write(signData);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.flush();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* unpack a xxx.lvbundle
*
* @param file
* @param url
* @return
*/
public static ScriptBundle unpackBundleRaw(boolean isBytecode, final String url, final File file) throws IOException {
DebugUtil.tsi("luaviewp-unpackBundle-raw");
if (file == null || url == null) {
return null;
}
final ScriptBundle scriptBundle = new ScriptBundle();
final FileInputStream inputStream = new FileInputStream(file);
final MappedByteBuffer buffer = inputStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
final String scriptBundleFolderPath = LuaScriptManager.buildScriptBundleFolderPath(url);
scriptBundle.setUrl(url);
scriptBundle.setBytecode(isBytecode);
scriptBundle.setBaseFilePath(scriptBundleFolderPath);
int fileNameLen = 0;
int fileLen = 0;
String fileNameStr = null;
byte[] fileName, fileData, signData;
int count = buffer.getInt();
for (int i = 0; i < count; i++) {//all files
fileNameLen = buffer.getInt();// get file name
fileName = new byte[fileNameLen];
buffer.get(fileName);
fileLen = buffer.getInt();// get file data
fileData = new byte[fileLen];
buffer.get(fileData);
if (!isBytecode) {//非二进制的还有sign的data
fileLen = buffer.getInt();
signData = new byte[fileLen];
buffer.get(signData);
} else {
signData = null;
}
fileNameStr = new String(fileName);
if (fileNameStr == null || fileNameStr.indexOf("../") != -1) {
return null;
}
scriptBundle.addScript(new ScriptFile(url, scriptBundleFolderPath, fileNameStr, fileData, signData));
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
DebugUtil.tei("luaviewp-unpackBundle-raw");
return scriptBundle;
}
}