Skip to content

Commit

Permalink
fix(core.manager): 判断zip插件包是否已解压时不再依赖tag文件
Browse files Browse the repository at this point in the history
单独提取zip中的config.json后先生成PluginConfig。
PluginConfig添加的isUnpacked方法根据各目标文件是否均已存在来判断是否已解压。
如果文件缺失,则会重新解压文件。

fix #691
  • Loading branch information
shifujun committed Jan 11, 2022
1 parent 445f4d9 commit 522071a
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
Expand Down Expand Up @@ -141,7 +142,7 @@ private void initDbWithConfigJson(int... configJsonResId)
InstalledDao installedDao = new InstalledDao(dbHelper);

for (String configJson : configs) {
installedDao.insert(PluginConfig.parseFromJson(configJson, context.getCacheDir()), null, null);
installedDao.insert(PluginConfig.parseFromJson(new JSONObject(configJson), context.getCacheDir()), null, null);
}

dbHelper.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.tencent.shadow.core.manager.installplugin.UnpackManager;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -104,7 +105,21 @@ public final PluginConfig installPluginFromDir(File dir) {
* @return PluginConfig
*/
public final PluginConfig installPluginFromZip(File zip, String hash) throws IOException, JSONException {
return mUnpackManager.unpackPlugin(hash, zip);
String zipHash;
if (hash != null) {
zipHash = hash;
} else {
zipHash = mUnpackManager.zipHash(zip);
}
File pluginUnpackDir = mUnpackManager.getPluginUnpackDir(zipHash, zip);
JSONObject configJson = mUnpackManager.getConfigJson(zip);
PluginConfig pluginConfig = PluginConfig.parseFromJson(configJson, pluginUnpackDir);

if (!pluginConfig.isUnpacked()) {
mUnpackManager.unpackPlugin(zip, pluginUnpackDir);
}

return pluginConfig;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ public class PluginConfig {
*/
public File storageDir;

public boolean isUnpacked() {
boolean pluginLoaderUnpacked = true;
if (pluginLoader != null) {
pluginLoaderUnpacked = pluginLoader.file.exists();
}

boolean runtimeUnpacked = true;
if (runTime != null) {
runtimeUnpacked = runTime.file.exists();
}

boolean pluginsUnpacked = true;
for (PluginFileInfo pluginFileInfo : plugins.values()) {
pluginsUnpacked = pluginsUnpacked && pluginFileInfo.file.exists();
}
return pluginLoaderUnpacked && runtimeUnpacked && pluginsUnpacked;
}

public static class FileInfo {
public final File file;
public final String hash;
Expand Down Expand Up @@ -88,33 +106,31 @@ public static class PluginFileInfo extends FileInfo {
}
}


public static PluginConfig parseFromJson(String json, File storageDir) throws JSONException {
JSONObject jsonObject = new JSONObject(json);
public static PluginConfig parseFromJson(JSONObject configJson, File storageDir) throws JSONException {
PluginConfig pluginConfig = new PluginConfig();
pluginConfig.version = jsonObject.getInt("version");
JSONArray compact_version_json = jsonObject.optJSONArray("compact_version");
pluginConfig.version = configJson.getInt("version");
JSONArray compact_version_json = configJson.optJSONArray("compact_version");
if (compact_version_json != null && compact_version_json.length() > 0) {
pluginConfig.compact_version = new int[compact_version_json.length()];
for (int i = 0; i < compact_version_json.length(); i++) {
pluginConfig.compact_version[i] = compact_version_json.getInt(i);
}
}
//todo #27 json的版本检查和不兼容检查
pluginConfig.UUID = jsonObject.getString("UUID");
pluginConfig.UUID_NickName = jsonObject.getString("UUID_NickName");
pluginConfig.UUID = configJson.getString("UUID");
pluginConfig.UUID_NickName = configJson.getString("UUID_NickName");

JSONObject loaderJson = jsonObject.optJSONObject("pluginLoader");
JSONObject loaderJson = configJson.optJSONObject("pluginLoader");
if (loaderJson != null) {
pluginConfig.pluginLoader = getFileInfo(loaderJson, storageDir);
}

JSONObject runtimeJson = jsonObject.optJSONObject("runtime");
JSONObject runtimeJson = configJson.optJSONObject("runtime");
if (runtimeJson != null) {
pluginConfig.runTime = getFileInfo(runtimeJson, storageDir);
}

JSONArray pluginArray = jsonObject.optJSONArray("plugins");
JSONArray pluginArray = configJson.optJSONArray("plugins");
if (pluginArray != null && pluginArray.length() > 0) {
for (int i = 0; i < pluginArray.length(); i++) {
JSONObject plugin = pluginArray.getJSONObject(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@

package com.tencent.shadow.core.manager.installplugin;

import static com.tencent.shadow.core.utils.Md5.md5File;

import com.tencent.shadow.core.common.Logger;
import com.tencent.shadow.core.common.LoggerFactory;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static com.tencent.shadow.core.utils.Md5.md5File;


public class UnpackManager {

private static final Logger mLogger = LoggerFactory.getLogger(UnpackManager.class);

private static final String UNPACK_DONE_PRE_FIX = "unpacked.";
private static final String CONFIG_FILENAME = "config.json";//todo #28 json的格式需要沉淀文档。
private static final String DEFAULT_STORE_DIR_NAME = "ShadowPluginManager";

Expand Down Expand Up @@ -69,56 +69,48 @@ public File getAppDir() {
* @param target Target
* @return 插件解包的目标目录
*/
File getPluginUnpackDir(String appHash, File target) {
public File getPluginUnpackDir(String appHash, File target) {
return new File(getVersionDir(appHash), target.getName());
}

/**
* 判断一个插件是否已经解包了
*
* @param target Target
* @return <code>true</code>表示已经解包了,即无需下载。
*/
boolean isPluginUnpacked(String versionHash, File target) {
File pluginUnpackDir = getPluginUnpackDir(versionHash, target);
return isDirUnpacked(pluginUnpackDir);
public String zipHash(File zip) {
return md5File(zip);
}

/**
* 判断一个插件解包目录是否解包了
*
* @param pluginUnpackDir 插件解包目录
* @return <code>true</code>表示已经解包了,即无需下载。
*/
boolean isDirUnpacked(File pluginUnpackDir) {
File tag = getUnpackedTag(pluginUnpackDir);
return tag.exists();
}
public JSONObject getConfigJson(File zip) {
ZipFile zipFile = null;
try {
zipFile = new SafeZipFile(zip);
ZipEntry entry = zipFile.getEntry(CONFIG_FILENAME);
InputStream inputStream = zipFile.getInputStream(entry);
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
for (String line; (line = br.readLine()) != null; ) {
sb.append(line);
}

return new JSONObject(sb.toString());
} catch (JSONException | IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (zipFile != null) {
zipFile.close();
}
} catch (IOException e) {
mLogger.warn("zip关闭时出错忽略", e);
}
}
}

/**
* 解包一个下载好的插件
* @param zipHash 插件包的hash
* @param target 插件包
*
* @param target 插件包
* @param pluginUnpackDir 解压目录
*/
public PluginConfig unpackPlugin(String zipHash, File target) throws IOException, JSONException {
if (zipHash == null) {
zipHash = md5File(target);
}
File pluginUnpackDir = getPluginUnpackDir(zipHash, target);

public void unpackPlugin(File target, File pluginUnpackDir) throws IOException {
pluginUnpackDir.mkdirs();
File tag = getUnpackedTag(pluginUnpackDir);

if (isDirUnpacked(pluginUnpackDir)) {
try {
return getDownloadedPluginInfoFromPluginUnpackedDir(pluginUnpackDir, zipHash);
} catch (Exception e) {
if (!tag.delete()) {
throw new IOException("解析版本信息失败,且无法删除标记:" + tag.getAbsolutePath());
}
}
}
MinFileUtils.cleanDirectory(pluginUnpackDir);

ZipFile zipFile = null;
Expand All @@ -131,13 +123,6 @@ public PluginConfig unpackPlugin(String zipHash, File target) throws IOException
MinFileUtils.writeOutZipEntry(zipFile, entry, pluginUnpackDir, entry.getName());
}
}

PluginConfig pluginConfig = getDownloadedPluginInfoFromPluginUnpackedDir(pluginUnpackDir, zipHash);

// 外边创建完成标记
tag.createNewFile();

return pluginConfig;
} finally {
try {
if (zipFile != null) {
Expand All @@ -149,26 +134,4 @@ public PluginConfig unpackPlugin(String zipHash, File target) throws IOException
}
}

File getUnpackedTag(File pluginUnpackDir) {
return new File(pluginUnpackDir.getParentFile(), UNPACK_DONE_PRE_FIX + pluginUnpackDir.getName());
}

PluginConfig getDownloadedPluginInfoFromPluginUnpackedDir(File pluginUnpackDir, String appHash)
throws IOException, JSONException {
File config = new File(pluginUnpackDir, CONFIG_FILENAME);
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(config)));
StringBuilder stringBuilder = new StringBuilder("");
String lineStr;
try {
while ((lineStr = br.readLine()) != null) {
stringBuilder.append(lineStr).append("\n");
}
} finally {
//noinspection ThrowFromFinallyBlock
br.close();
}
String versionJson = stringBuilder.toString();
return PluginConfig.parseFromJson(versionJson, pluginUnpackDir);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,15 @@ private void launchJumpActivity(String partKey, Intent pluginIntent) {
intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey);
intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, pluginIntent.getComponent().getClassName());
intent.putExtra(Constant.KEY_EXTRAS, pluginIntent.getExtras());
intent.putExtra(Constant.KEY_FROM_ID, getFromId());
ActivityScenario.launch(intent);
}

protected Class<? extends Activity> getJumpActivityClass() {
return JumpToPluginActivity.class;
}

protected int getFromId() {
return Constant.FROM_ID_START_ACTIVITY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Tencent is pleased to support the open source community by making Tencent Shadow available.
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.tencent.shadow.test.cases.plugin_main;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;

import com.tencent.shadow.test.lib.constant.Constant;

import org.junit.runner.RunWith;


@RunWith(AndroidJUnit4.class)
@LargeTest
public class ReinstallPluginTest extends BasicTest {

@Override
protected int getFromId() {
return Constant.FROM_ID_REINSTALL_PLUGIN;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ protected String getPartKey() {
protected Class<? extends Activity> getJumpActivityClass() {
return BindPluginServiceActivity.class;
}

@Override
protected int getFromId() {
return Constant.FROM_ID_BIND_SERVICE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.tencent.shadow.test.dynamic.host;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

Expand All @@ -40,15 +41,18 @@ public void jump(View view) {
HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);

Bundle bundle = new Bundle();
Intent intent = getIntent();
bundle.putString(Constant.KEY_PLUGIN_ZIP_PATH, PluginHelper.getInstance().pluginZipFile.getAbsolutePath());
bundle.putString(Constant.KEY_PLUGIN_PART_KEY, getIntent().getStringExtra(Constant.KEY_PLUGIN_PART_KEY));
bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));
bundle.putBundle(Constant.KEY_EXTRAS, getIntent().getBundleExtra(Constant.KEY_EXTRAS));
bundle.putString(Constant.KEY_PLUGIN_PART_KEY, intent.getStringExtra(Constant.KEY_PLUGIN_PART_KEY));
bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, intent.getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));
bundle.putBundle(Constant.KEY_EXTRAS, intent.getBundleExtra(Constant.KEY_EXTRAS));

int fromId = intent.getIntExtra(Constant.KEY_FROM_ID, Constant.FROM_ID_NOOP);

final SimpleIdlingResource idlingResource = HostApplication.getApp().mIdlingResource;
idlingResource.setIdleState(false);
HostApplication.getApp().getPluginManager()
.enter(this, Constant.FROM_ID_BIND_SERVICE, bundle, new EnterCallback() {
.enter(this, fromId, bundle, new EnterCallback() {
@Override
public void onShowLoadingView(View view) {

Expand Down
Loading

0 comments on commit 522071a

Please sign in to comment.