You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1219 lines
35 KiB
1219 lines
35 KiB
/*
|
|
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* 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.arialyy.aria.util;
|
|
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.net.Uri;
|
|
import android.os.Environment;
|
|
import android.os.StatFs;
|
|
import android.text.TextUtils;
|
|
import android.util.Base64;
|
|
import com.arialyy.aria.core.AriaManager;
|
|
import com.arialyy.aria.core.FtpUrlEntity;
|
|
import com.arialyy.aria.core.command.ICmd;
|
|
import com.arialyy.aria.core.command.group.AbsGroupCmd;
|
|
import com.arialyy.aria.core.command.group.GroupCmdFactory;
|
|
import com.arialyy.aria.core.command.normal.AbsNormalCmd;
|
|
import com.arialyy.aria.core.command.normal.NormalCmdFactory;
|
|
import com.arialyy.aria.core.common.AbsFileer;
|
|
import com.arialyy.aria.core.common.TaskRecord;
|
|
import com.arialyy.aria.core.common.ThreadRecord;
|
|
import com.arialyy.aria.core.download.DownloadEntity;
|
|
import com.arialyy.aria.core.download.DownloadGroupEntity;
|
|
import com.arialyy.aria.core.inf.AbsGroupTaskWrapper;
|
|
import com.arialyy.aria.core.inf.AbsNormalEntity;
|
|
import com.arialyy.aria.core.inf.AbsTaskWrapper;
|
|
import com.arialyy.aria.core.upload.UploadEntity;
|
|
import com.arialyy.aria.orm.DbEntity;
|
|
import dalvik.system.DexFile;
|
|
import java.io.File;
|
|
import java.io.FileFilter;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.io.Serializable;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.ParameterizedType;
|
|
import java.lang.reflect.Type;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.net.URLEncoder;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Properties;
|
|
import java.util.Set;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
* Created by lyy on 2016/1/22. 通用工具
|
|
*/
|
|
public class CommonUtil {
|
|
private static final String TAG = "CommonUtil";
|
|
|
|
/**
|
|
* 检查分块任务是否存在
|
|
*
|
|
* @param filePath 文件保存路径
|
|
* @return {@code true} 分块文件存在
|
|
*/
|
|
public static boolean blockTaskExists(String filePath) {
|
|
return new File(String.format(AbsFileer.SUB_PATH, filePath, 0)).exists();
|
|
}
|
|
|
|
/**
|
|
* 删除文件
|
|
*
|
|
* @param path 文件路径
|
|
* @return {@code true}删除成功、{@code false}删除失败
|
|
*/
|
|
public static boolean deleteFile(String path) {
|
|
if (TextUtils.isEmpty(path)) {
|
|
ALog.e(TAG, "删除文件失败,路径为空");
|
|
return false;
|
|
}
|
|
File file = new File(path);
|
|
if (file.exists()) {
|
|
final File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
|
|
if (file.renameTo(to)) {
|
|
return to.delete();
|
|
} else {
|
|
return file.delete();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 将对象写入文件
|
|
*
|
|
* @param filePath 文件路径
|
|
* @param data data数据必须实现{@link Serializable}接口
|
|
*/
|
|
public static void writeObjToFile(String filePath, Object data) {
|
|
if (!(data instanceof Serializable)) {
|
|
ALog.e(TAG, "对象写入文件失败,data数据必须实现Serializable接口");
|
|
return;
|
|
}
|
|
FileOutputStream ops = null;
|
|
try {
|
|
if (!createFile(filePath)) {
|
|
return;
|
|
}
|
|
ops = new FileOutputStream(filePath);
|
|
ObjectOutputStream oops = new ObjectOutputStream(ops);
|
|
oops.writeObject(data);
|
|
} catch (FileNotFoundException e) {
|
|
e.printStackTrace();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
if (ops != null) {
|
|
try {
|
|
ops.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从文件中读取对象
|
|
*
|
|
* @param filePath 文件路径
|
|
* @return 如果读取成功,返回相应的Obj对象,读取失败,返回null
|
|
*/
|
|
public static Object readObjFromFile(String filePath) {
|
|
if (TextUtils.isEmpty(filePath)) {
|
|
ALog.e(TAG, "文件路径为空");
|
|
return null;
|
|
}
|
|
File file = new File(filePath);
|
|
if (!file.exists()) {
|
|
ALog.e(TAG, String.format("文件【%s】不存在", filePath));
|
|
return null;
|
|
}
|
|
FileInputStream fis = null;
|
|
try {
|
|
fis = new FileInputStream(filePath);
|
|
ObjectInputStream oois = new ObjectInputStream(fis);
|
|
return oois.readObject();
|
|
} catch (FileNotFoundException e) {
|
|
e.printStackTrace();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
} catch (ClassNotFoundException e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
if (fis != null) {
|
|
try {
|
|
fis.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 获取分块文件的快大小
|
|
*
|
|
* @param fileLen 文件总长度
|
|
* @param blockId 分块id
|
|
* @param blockNum 分块数量
|
|
* @return 分块长度
|
|
*/
|
|
public static long getBlockLen(long fileLen, int blockId, int blockNum) {
|
|
final long averageLen = fileLen / blockNum;
|
|
return blockId == blockNum - 1 ? (fileLen - blockId * averageLen) : averageLen;
|
|
}
|
|
|
|
/**
|
|
* 检查SD内存空间是否充足
|
|
*
|
|
* @param filePath 文件保存路径
|
|
* @param fileSize 文件大小
|
|
* @return {@code false} 内存空间不足,{@code true}内存空间足够
|
|
*/
|
|
public static boolean checkSDMemorySpace(String filePath, long fileSize) {
|
|
List<String> dirs = FileUtil.getSDPathList(AriaManager.APP);
|
|
if (dirs == null || dirs.isEmpty()) {
|
|
return true;
|
|
}
|
|
for (String path : dirs) {
|
|
if (filePath.contains(path)) {
|
|
if (fileSize > 0 && fileSize > getAvailableExternalMemorySize(path)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* sdcard 可用大小
|
|
*
|
|
* @param sdcardPath sdcard 根路径
|
|
* @return 单位为:byte
|
|
*/
|
|
public static long getAvailableExternalMemorySize(String sdcardPath) {
|
|
StatFs stat = new StatFs(sdcardPath);
|
|
long blockSize = stat.getBlockSize();
|
|
long availableBlocks = stat.getAvailableBlocks();
|
|
return availableBlocks * blockSize;
|
|
}
|
|
|
|
/**
|
|
* sdcard 总大小
|
|
*
|
|
* @param sdcardPath sdcard 根路径
|
|
* @return 单位为:byte
|
|
*/
|
|
public static long getTotalExternalMemorySize(String sdcardPath) {
|
|
StatFs stat = new StatFs(sdcardPath);
|
|
long blockSize = stat.getBlockSize();
|
|
long totalBlocks = stat.getBlockCount();
|
|
return totalBlocks * blockSize;
|
|
}
|
|
|
|
/**
|
|
* 获取某包下所有类
|
|
*
|
|
* @param className 过滤的类名
|
|
* @return 类的完整名称
|
|
*/
|
|
public static List<String> getPkgClassNames(Context context, String className) {
|
|
List<String> classNameList = new ArrayList<>();
|
|
String pPath = context.getPackageCodePath();
|
|
File dir = new File(pPath).getParentFile();
|
|
String[] paths = dir.list();
|
|
if (paths == null) {
|
|
classNameList.addAll(getPkgClassName(pPath, className));
|
|
} else {
|
|
String dPath = dir.getPath();
|
|
for (String path : dir.list()) {
|
|
String fPath = dPath + "/" + path;
|
|
if (!fPath.endsWith(".apk")) {
|
|
continue;
|
|
}
|
|
classNameList.addAll(getPkgClassName(fPath, className));
|
|
}
|
|
}
|
|
return classNameList;
|
|
}
|
|
|
|
/**
|
|
* 获取指定包名下的所有类
|
|
*
|
|
* @param path dex路径
|
|
* @param filterClass 需要过滤的类
|
|
*/
|
|
public static List<String> getPkgClassName(String path, String filterClass) {
|
|
List<String> list = new ArrayList<>();
|
|
try {
|
|
File file = new File(path);
|
|
if (!file.exists()) {
|
|
ALog.w(TAG, String.format("路径【%s】下的Dex文件不存在", path));
|
|
return list;
|
|
}
|
|
|
|
DexFile df = new DexFile(path);//通过DexFile查找当前的APK中可执行文件
|
|
Enumeration<String> enumeration = df.entries();//获取df中的元素 这里包含了所有可执行的类名 该类名包含了包名+类名的方式
|
|
while (enumeration.hasMoreElements()) {
|
|
String _className = enumeration.nextElement();
|
|
if (!_className.contains(filterClass)) {
|
|
continue;
|
|
}
|
|
if (_className.contains(filterClass)) {
|
|
list.add(_className);
|
|
}
|
|
}
|
|
df.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* 拦截window.location.replace数据
|
|
*
|
|
* @return 重定向url
|
|
*/
|
|
public static String getWindowReplaceUrl(String text) {
|
|
if (TextUtils.isEmpty(text)) {
|
|
ALog.e(TAG, "拦截数据为null");
|
|
return null;
|
|
}
|
|
String reg = Regular.REG_WINLOD_REPLACE;
|
|
Pattern p = Pattern.compile(reg);
|
|
Matcher m = p.matcher(text);
|
|
if (m.find()) {
|
|
String s = m.group();
|
|
s = s.substring(9, s.length() - 2);
|
|
return s;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 获取sdcard app的缓存目录
|
|
*
|
|
* @return "/mnt/sdcard/Android/data/{package_name}/files/"
|
|
*/
|
|
public static String getAppPath(Context context) {
|
|
//判断是否存在sd卡
|
|
boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(
|
|
android.os.Environment.getExternalStorageState());
|
|
if (!sdExist) {
|
|
return null;
|
|
} else {
|
|
//获取sd卡路径
|
|
File file = context.getExternalFilesDir(null);
|
|
String dir;
|
|
if (file != null) {
|
|
dir = file.getPath() + "/";
|
|
} else {
|
|
dir = Environment.getExternalStorageDirectory().getPath()
|
|
+ "/Android/data/"
|
|
+ context.getPackageName()
|
|
+ "/files/";
|
|
}
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取map泛型类型
|
|
*
|
|
* @param map list类型字段
|
|
* @return 泛型类型
|
|
*/
|
|
public static Class[] getMapParamType(Field map) {
|
|
Class type = map.getType();
|
|
if (!type.isAssignableFrom(Map.class)) {
|
|
ALog.d(TAG, "字段类型不是Map");
|
|
return null;
|
|
}
|
|
|
|
Type fc = map.getGenericType();
|
|
|
|
if (fc == null) {
|
|
ALog.d(TAG, "该字段没有泛型参数");
|
|
return null;
|
|
}
|
|
|
|
if (fc instanceof ParameterizedType) {
|
|
ParameterizedType pt = (ParameterizedType) fc;
|
|
Type[] types = pt.getActualTypeArguments();
|
|
Class[] clazz = new Class[2];
|
|
clazz[0] = (Class) types[0];
|
|
clazz[1] = (Class) types[1];
|
|
return clazz;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 获取list泛型类型
|
|
*
|
|
* @param list list类型字段
|
|
* @return 泛型类型
|
|
*/
|
|
|
|
public static Class getListParamType(Field list) {
|
|
Class type = list.getType();
|
|
if (!type.isAssignableFrom(List.class)) {
|
|
ALog.d(TAG, "字段类型不是List");
|
|
return null;
|
|
}
|
|
|
|
Type fc = list.getGenericType(); // 关键的地方,如果是List类型,得到其Generic的类型
|
|
|
|
if (fc == null) {
|
|
ALog.d(TAG, "该字段没有泛型参数");
|
|
return null;
|
|
}
|
|
|
|
if (fc instanceof ParameterizedType) { //如果是泛型参数的类型
|
|
ParameterizedType pt = (ParameterizedType) fc;
|
|
return (Class) pt.getActualTypeArguments()[0]; //得到泛型里的class类型对象。
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 创建文件名,如果url链接有后缀名,则使用url中的后缀名
|
|
*
|
|
* @return url 的 hashKey
|
|
*/
|
|
public static String createFileName(String url) {
|
|
int end = url.indexOf("?");
|
|
String tempUrl, fileName = "";
|
|
if (end > 0) {
|
|
tempUrl = url.substring(0, end);
|
|
int tempEnd = tempUrl.lastIndexOf("/");
|
|
if (tempEnd > 0) {
|
|
fileName = tempUrl.substring(tempEnd + 1);
|
|
}
|
|
} else {
|
|
int tempEnd = url.lastIndexOf("/");
|
|
if (tempEnd > 0) {
|
|
fileName = url.substring(tempEnd + 1);
|
|
}
|
|
}
|
|
if (TextUtils.isEmpty(fileName)) {
|
|
fileName = CommonUtil.keyToHashKey(url);
|
|
}
|
|
return fileName;
|
|
}
|
|
|
|
/**
|
|
* 分割获取url,协议,ip/域名,端口,内容
|
|
*
|
|
* @param url 输入的url{@code String url = "ftp://z:z@dygod18.com:21211/[电影天堂www.dy2018.com]猩球崛起3:终极之战BD国英双语中英双字.mkv";}
|
|
*/
|
|
public static FtpUrlEntity getFtpUrlInfo(String url) {
|
|
Uri uri = Uri.parse(url);
|
|
|
|
String userInfo = uri.getUserInfo(), remotePath = uri.getPath();
|
|
ALog.d(TAG,
|
|
String.format("scheme = %s, user = %s, host = %s, port = %s, path = %s", uri.getScheme(),
|
|
userInfo, uri.getHost(), uri.getPort(), remotePath));
|
|
|
|
FtpUrlEntity entity = new FtpUrlEntity();
|
|
entity.url = url;
|
|
entity.hostName = uri.getHost();
|
|
entity.port = uri.getPort() == -1 ? "21" : String.valueOf(uri.getPort());
|
|
if (!TextUtils.isEmpty(userInfo)) {
|
|
String[] temp = userInfo.split(":");
|
|
if (temp.length == 2) {
|
|
entity.user = temp[0];
|
|
entity.password = temp[1];
|
|
} else {
|
|
entity.user = userInfo;
|
|
}
|
|
}
|
|
entity.scheme = uri.getScheme();
|
|
entity.remotePath = TextUtils.isEmpty(remotePath) ? "/" : remotePath;
|
|
return entity;
|
|
}
|
|
|
|
/**
|
|
* 转换Url
|
|
*
|
|
* @param url 原地址
|
|
* @return 转换后的地址
|
|
*/
|
|
public static String convertUrl(String url) {
|
|
Uri uri = Uri.parse(url);
|
|
url = uri.toString();
|
|
if (hasDoubleCharacter(url)) {
|
|
//预先处理空格,URLEncoder只会把空格转换为+
|
|
url = url.replaceAll(" ", "%20");
|
|
//匹配双字节字符(包括汉字在内)
|
|
String regex = Regular.REG_DOUBLE_CHAR_AND_SPACE;
|
|
Pattern p = Pattern.compile(regex);
|
|
Matcher m = p.matcher(url);
|
|
Set<String> strs = new HashSet<>();
|
|
while (m.find()) {
|
|
strs.add(m.group());
|
|
}
|
|
try {
|
|
for (String str : strs) {
|
|
url = url.replaceAll(str, URLEncoder.encode(str, "UTF-8"));
|
|
}
|
|
} catch (UnsupportedEncodingException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* 判断是否有双字节字符(包括汉字在内) 和空格、制表符、回车
|
|
*
|
|
* @param chineseStr 需要进行判断的字符串
|
|
* @return {@code true}有双字节字符,{@code false} 无双字节字符
|
|
*/
|
|
public static boolean hasDoubleCharacter(String chineseStr) {
|
|
char[] charArray = chineseStr.toCharArray();
|
|
for (char aCharArray : charArray) {
|
|
if (((aCharArray >= 0x0391) && (aCharArray <= 0xFFE5)) || (aCharArray == 0x0d) || (aCharArray
|
|
== 0x0a) || (aCharArray == 0x20)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* base64 解密字符串
|
|
*
|
|
* @param str 被加密的字符串
|
|
* @return 解密后的字符串
|
|
*/
|
|
public static String decryptBASE64(String str) {
|
|
return new String(Base64.decode(str.getBytes(), Base64.DEFAULT));
|
|
}
|
|
|
|
/**
|
|
* base64 加密字符串
|
|
*
|
|
* @param str 需要加密的字符串
|
|
* @return 加密后的字符串
|
|
*/
|
|
public static String encryptBASE64(String str) {
|
|
return Base64.encodeToString(str.getBytes(), Base64.DEFAULT);
|
|
}
|
|
|
|
/**
|
|
* 字符串编码转换
|
|
*/
|
|
public static String strCharSetConvert(String oldStr, String charSet) {
|
|
try {
|
|
return new String(oldStr.getBytes(), charSet);
|
|
} catch (UnsupportedEncodingException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 根据下载任务组的url创建key
|
|
*
|
|
* @return urls 为 null 或者 size为0,返回""
|
|
*/
|
|
public static String getMd5Code(List<String> urls) {
|
|
if (urls == null || urls.size() < 1) return "";
|
|
String md5 = "";
|
|
StringBuilder sb = new StringBuilder();
|
|
for (String url : urls) {
|
|
sb.append(url);
|
|
}
|
|
try {
|
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
md.update(sb.toString().getBytes());
|
|
md5 = new BigInteger(1, md.digest()).toString(16);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
ALog.e(TAG, e.getMessage());
|
|
}
|
|
return md5;
|
|
}
|
|
|
|
/**
|
|
* 获取字符串的md5
|
|
*
|
|
* @return 字符串为空或获取md5失败,则返回""
|
|
*/
|
|
public static String getStrMd5(String str) {
|
|
if (TextUtils.isEmpty(str)) return "";
|
|
try {
|
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
md.update(str.getBytes());
|
|
return new BigInteger(1, md.digest()).toString(16);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
ALog.e(TAG, e.getMessage());
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* 删除任务组记录
|
|
*
|
|
* @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录
|
|
*/
|
|
public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile) {
|
|
if (groupEntity == null) {
|
|
ALog.e(TAG, "删除下载任务组记录失败,任务组实体为null");
|
|
return;
|
|
}
|
|
List<TaskRecord> records =
|
|
DbEntity.findDatas(TaskRecord.class, "dGroupHash=?", groupEntity.getGroupHash());
|
|
|
|
if (records == null || records.isEmpty()) {
|
|
ALog.w(TAG, "组任务记录删除失败,记录为null");
|
|
} else {
|
|
for (TaskRecord record : records) {
|
|
// 删除分块文件
|
|
if (record.isBlock) {
|
|
for (int i = 0, len = record.threadNum; i < len; i++) {
|
|
File partFile = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i));
|
|
if (partFile.exists()) {
|
|
partFile.delete();
|
|
}
|
|
}
|
|
}
|
|
record.deleteData();
|
|
}
|
|
}
|
|
|
|
List<DownloadEntity> subs = groupEntity.getSubEntities();
|
|
if (subs != null) {
|
|
for (DownloadEntity sub : subs) {
|
|
File file = new File(sub.getDownloadPath());
|
|
if (file.exists() && (removeFile || !sub.isComplete())) {
|
|
file.delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(groupEntity.getDirPath())) {
|
|
File dir = new File(groupEntity.getDirPath());
|
|
if (dir.exists() && (removeFile || !groupEntity.isComplete())) {
|
|
dir.delete();
|
|
}
|
|
groupEntity.deleteData();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除任务记录
|
|
*
|
|
* @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录
|
|
*/
|
|
public static void delTaskRecord(AbsNormalEntity dEntity, boolean removeFile) {
|
|
if (dEntity == null) return;
|
|
String filePath;
|
|
int type;
|
|
if (dEntity instanceof DownloadEntity) {
|
|
type = 1;
|
|
filePath = ((DownloadEntity) dEntity).getDownloadPath();
|
|
} else if (dEntity instanceof UploadEntity) {
|
|
type = 2;
|
|
filePath = ((UploadEntity) dEntity).getFilePath();
|
|
} else {
|
|
ALog.w(TAG, "删除记录失败,未知类型");
|
|
return;
|
|
}
|
|
delTaskRecord(filePath, type, removeFile);
|
|
}
|
|
|
|
/**
|
|
* 删除任务记录,默认删除文件
|
|
*
|
|
* @param filePath 文件路径
|
|
* @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录
|
|
* @param type {@code 1}下载任务的记录,{@code 2} 上传任务的记录 {@code false}如果任务已经完成,只删除任务数据库记录
|
|
*/
|
|
public static void delTaskRecord(String filePath, int type, boolean removeFile) {
|
|
if (TextUtils.isEmpty(filePath)) {
|
|
throw new NullPointerException("删除记录失败,文件路径为空");
|
|
}
|
|
if (type != 1 && type != 2) {
|
|
throw new IllegalArgumentException("任务记录类型错误");
|
|
}
|
|
TaskRecord record = DbEntity.findFirst(TaskRecord.class, "filePath=?", filePath);
|
|
DbEntity.deleteData(ThreadRecord.class, "key=?", filePath);
|
|
|
|
File file = new File(filePath);
|
|
if (record == null) {
|
|
ALog.w(TAG, "记录为空");
|
|
} else {
|
|
// 删除分块文件
|
|
if (record.isBlock) {
|
|
for (int i = 0, len = record.threadNum; i < len; i++) {
|
|
File partFile = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i));
|
|
if (partFile.exists()) {
|
|
partFile.delete();
|
|
}
|
|
}
|
|
}
|
|
record.deleteData();
|
|
}
|
|
|
|
if (file.exists() && removeFile) {
|
|
file.delete();
|
|
}
|
|
//下载任务实体和下载实体为一对一关系,下载实体删除,任务实体自动删除
|
|
if (type == 1) {
|
|
DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath);
|
|
} else {
|
|
DbEntity.deleteData(UploadEntity.class, "filePath=?", filePath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除任务记录,默认删除文件
|
|
*
|
|
* @param filePath 文件路径
|
|
* @param type {@code 1}下载任务的记录,{@code 2} 上传任务的记录 {@code false}如果任务已经完成,只删除任务数据库记录
|
|
*/
|
|
public static void delTaskRecord(String filePath, int type) {
|
|
delTaskRecord(filePath, type, false);
|
|
}
|
|
|
|
/**
|
|
* 获取CPU核心数
|
|
*/
|
|
public static int getCoresNum() {
|
|
//Private Class to display only CPU devices in the directory listing
|
|
class CpuFilter implements FileFilter {
|
|
@Override public boolean accept(File pathname) {
|
|
//Check if filename is "cpu", followed by a single digit number
|
|
return Pattern.matches("cpu[0-9]", pathname.getName());
|
|
}
|
|
}
|
|
|
|
try {
|
|
//Get directory containing CPU info
|
|
File dir = new File("/sys/devices/system/cpu/");
|
|
//Filter to only list the devices we care about
|
|
File[] files = dir.listFiles(new CpuFilter());
|
|
ALog.d(TAG, "CPU Count: " + files.length);
|
|
//Return the number of cores (virtual CPU devices)
|
|
return files.length;
|
|
} catch (Exception e) {
|
|
//Print exception
|
|
ALog.d(TAG, "CPU Count: Failed.");
|
|
e.printStackTrace();
|
|
//Default to return 1 core
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 通过流创建文件
|
|
*/
|
|
public static void createFileFormInputStream(InputStream is, String path) {
|
|
try {
|
|
FileOutputStream fos = new FileOutputStream(path);
|
|
byte[] buf = new byte[1024];
|
|
int len;
|
|
while ((len = is.read(buf)) > 0) {
|
|
fos.write(buf, 0, len);
|
|
}
|
|
is.close();
|
|
fos.flush();
|
|
fos.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 校验文件MD5码
|
|
*/
|
|
public static boolean checkMD5(String md5, File updateFile) {
|
|
if (TextUtils.isEmpty(md5) || updateFile == null) {
|
|
ALog.e(TAG, "MD5 string empty or updateFile null");
|
|
return false;
|
|
}
|
|
|
|
String calculatedDigest = getFileMD5(updateFile);
|
|
if (calculatedDigest == null) {
|
|
ALog.e(TAG, "calculatedDigest null");
|
|
return false;
|
|
}
|
|
return calculatedDigest.equalsIgnoreCase(md5);
|
|
}
|
|
|
|
/**
|
|
* 校验文件MD5码
|
|
*/
|
|
public static boolean checkMD5(String md5, InputStream is) {
|
|
if (TextUtils.isEmpty(md5) || is == null) {
|
|
ALog.e(TAG, "MD5 string empty or updateFile null");
|
|
return false;
|
|
}
|
|
|
|
String calculatedDigest = getFileMD5(is);
|
|
if (calculatedDigest == null) {
|
|
ALog.e(TAG, "calculatedDigest null");
|
|
return false;
|
|
}
|
|
return calculatedDigest.equalsIgnoreCase(md5);
|
|
}
|
|
|
|
/**
|
|
* 获取文件MD5码
|
|
*/
|
|
public static String getFileMD5(File updateFile) {
|
|
InputStream is;
|
|
try {
|
|
is = new FileInputStream(updateFile);
|
|
} catch (FileNotFoundException e) {
|
|
ALog.e(TAG, e);
|
|
return null;
|
|
}
|
|
|
|
return getFileMD5(is);
|
|
}
|
|
|
|
/**
|
|
* 获取文件MD5码
|
|
*/
|
|
public static String getFileMD5(InputStream is) {
|
|
MessageDigest digest;
|
|
try {
|
|
digest = MessageDigest.getInstance("MD5");
|
|
} catch (NoSuchAlgorithmException e) {
|
|
ALog.e(TAG, e);
|
|
return null;
|
|
}
|
|
|
|
byte[] buffer = new byte[8192];
|
|
int read;
|
|
try {
|
|
while ((read = is.read(buffer)) > 0) {
|
|
digest.update(buffer, 0, read);
|
|
}
|
|
byte[] md5sum = digest.digest();
|
|
BigInteger bigInt = new BigInteger(1, md5sum);
|
|
String output = bigInt.toString(16);
|
|
// Fill to 32 chars
|
|
output = String.format("%32s", output).replace(' ', '0');
|
|
return output;
|
|
} catch (IOException e) {
|
|
throw new RuntimeException("Unable to process file for MD5", e);
|
|
} finally {
|
|
try {
|
|
is.close();
|
|
} catch (IOException e) {
|
|
ALog.e(TAG, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建任务命令
|
|
*
|
|
* @param taskType {@link ICmd#TASK_TYPE_DOWNLOAD}、{@link ICmd#TASK_TYPE_DOWNLOAD_GROUP}、{@link
|
|
* ICmd#TASK_TYPE_UPLOAD}
|
|
*/
|
|
public static <T extends AbsTaskWrapper> AbsNormalCmd createNormalCmd(T entity, int cmd,
|
|
int taskType) {
|
|
return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType);
|
|
}
|
|
|
|
/**
|
|
* 创建任务组命令
|
|
*
|
|
* @param childUrl 子任务url
|
|
*/
|
|
public static <T extends AbsGroupTaskWrapper> AbsGroupCmd createGroupCmd(String target, T entity,
|
|
int cmd, String childUrl) {
|
|
return GroupCmdFactory.getInstance().createCmd(target, entity, cmd, childUrl);
|
|
}
|
|
|
|
/**
|
|
* 创建隐性的Intent
|
|
*/
|
|
public static Intent createIntent(String packageName, String action) {
|
|
Uri.Builder builder = new Uri.Builder();
|
|
builder.scheme(packageName);
|
|
Uri uri = builder.build();
|
|
Intent intent = new Intent(action);
|
|
intent.setData(uri);
|
|
return intent;
|
|
}
|
|
|
|
/**
|
|
* 存储字符串到配置文件
|
|
*
|
|
* @param preName 配置文件名
|
|
* @param key 存储的键值
|
|
* @param value 需要存储的字符串
|
|
* @return 成功标志
|
|
*/
|
|
public static Boolean putString(String preName, Context context, String key, String value) {
|
|
SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE);
|
|
SharedPreferences.Editor editor = pre.edit();
|
|
editor.putString(key, value);
|
|
return editor.commit();
|
|
}
|
|
|
|
/**
|
|
* 从配置文件读取字符串
|
|
*
|
|
* @param preName 配置文件名
|
|
* @param key 字符串键值
|
|
* @return 键值对应的字符串, 默认返回""
|
|
*/
|
|
public static String getString(String preName, Context context, String key) {
|
|
SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE);
|
|
return pre.getString(key, "");
|
|
}
|
|
|
|
/**
|
|
* 获取所有字段,包括父类的字段
|
|
*/
|
|
public static List<Field> getAllFields(Class clazz) {
|
|
List<Field> fields = new ArrayList<>();
|
|
Class personClazz = clazz.getSuperclass();
|
|
if (personClazz != null) {
|
|
Class rootClazz = personClazz.getSuperclass();
|
|
if (rootClazz != null) {
|
|
Collections.addAll(fields, rootClazz.getDeclaredFields());
|
|
}
|
|
Collections.addAll(fields, personClazz.getDeclaredFields());
|
|
}
|
|
Collections.addAll(fields, clazz.getDeclaredFields());
|
|
return fields;
|
|
}
|
|
|
|
/**
|
|
* 获取当前类里面的所在字段
|
|
*/
|
|
public static Field[] getFields(Class clazz) {
|
|
Field[] fields = null;
|
|
fields = clazz.getDeclaredFields();
|
|
if (fields == null || fields.length == 0) {
|
|
Class superClazz = clazz.getSuperclass();
|
|
if (superClazz != null) {
|
|
fields = getFields(superClazz);
|
|
}
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
/**
|
|
* 获取类里面的指定对象,如果该类没有则从父类查询
|
|
*/
|
|
public static Field getField(Class clazz, String name) {
|
|
Field field = null;
|
|
try {
|
|
field = clazz.getDeclaredField(name);
|
|
} catch (NoSuchFieldException e) {
|
|
try {
|
|
field = clazz.getField(name);
|
|
} catch (NoSuchFieldException e1) {
|
|
if (clazz.getSuperclass() == null) {
|
|
return field;
|
|
} else {
|
|
field = getField(clazz.getSuperclass(), name);
|
|
}
|
|
}
|
|
}
|
|
if (field != null) {
|
|
field.setAccessible(true);
|
|
}
|
|
return field;
|
|
}
|
|
|
|
/**
|
|
* 字符串转hashcode
|
|
*/
|
|
public static int keyToHashCode(String str) {
|
|
int total = 0;
|
|
for (int i = 0; i < str.length(); i++) {
|
|
char ch = str.charAt(i);
|
|
if (ch == '-') ch = (char) 28; // does not contain the same last 5 bits as any letter
|
|
if (ch == '\'') ch = (char) 29; // nor this
|
|
total = (total * 33) + (ch & 0x1F);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* 将key转换为16进制码
|
|
*
|
|
* @param key 缓存的key
|
|
* @return 转换后的key的值, 系统便是通过该key来读写缓存
|
|
*/
|
|
public static String keyToHashKey(String key) {
|
|
String cacheKey;
|
|
try {
|
|
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
|
|
mDigest.update(key.getBytes());
|
|
cacheKey = bytesToHexString(mDigest.digest());
|
|
} catch (NoSuchAlgorithmException e) {
|
|
cacheKey = String.valueOf(key.hashCode());
|
|
}
|
|
return cacheKey;
|
|
}
|
|
|
|
/**
|
|
* 将普通字符串转换为16位进制字符串
|
|
*/
|
|
public static String bytesToHexString(byte[] src) {
|
|
StringBuilder stringBuilder = new StringBuilder("0x");
|
|
if (src == null || src.length <= 0) {
|
|
return null;
|
|
}
|
|
char[] buffer = new char[2];
|
|
for (byte aSrc : src) {
|
|
buffer[0] = Character.forDigit((aSrc >>> 4) & 0x0F, 16);
|
|
buffer[1] = Character.forDigit(aSrc & 0x0F, 16);
|
|
stringBuilder.append(buffer);
|
|
}
|
|
return stringBuilder.toString();
|
|
}
|
|
|
|
/**
|
|
* 获取对象名
|
|
*
|
|
* @param obj 对象
|
|
* @return 对象名
|
|
*/
|
|
public static String getClassName(Object obj) {
|
|
String arrays[] = obj.getClass().getName().split("\\.");
|
|
return arrays[arrays.length - 1];
|
|
}
|
|
|
|
/**
|
|
* 获取对象名
|
|
*
|
|
* @param clazz clazz
|
|
* @return 对象名
|
|
*/
|
|
public static String getClassName(Class clazz) {
|
|
String arrays[] = clazz.getName().split("\\.");
|
|
return arrays[arrays.length - 1];
|
|
}
|
|
|
|
/**
|
|
* 格式化文件大小
|
|
*
|
|
* @param size file.length() 获取文件大小
|
|
*/
|
|
public static String formatFileSize(double size) {
|
|
if (size < 0) {
|
|
return "0kb";
|
|
}
|
|
double kiloByte = size / 1024;
|
|
if (kiloByte < 1) {
|
|
return size + "b";
|
|
}
|
|
|
|
double megaByte = kiloByte / 1024;
|
|
if (megaByte < 1) {
|
|
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
|
|
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "kb";
|
|
}
|
|
|
|
double gigaByte = megaByte / 1024;
|
|
if (gigaByte < 1) {
|
|
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
|
|
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "mb";
|
|
}
|
|
|
|
double teraBytes = gigaByte / 1024;
|
|
if (teraBytes < 1) {
|
|
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
|
|
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "gb";
|
|
}
|
|
BigDecimal result4 = new BigDecimal(teraBytes);
|
|
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "tb";
|
|
}
|
|
|
|
/**
|
|
* 创建目录 当目录不存在的时候创建文件,否则返回false
|
|
*/
|
|
public static boolean createDir(String path) {
|
|
File file = new File(path);
|
|
if (!file.exists()) {
|
|
if (!file.mkdirs()) {
|
|
ALog.d(TAG, "创建失败,请检查路径和是否配置文件权限!");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 创建文件 当文件不存在的时候就创建一个文件。 如果文件存在,先删除原文件,然后重新创建一个新文件
|
|
*
|
|
* @return {@code true} 创建成功、{@code false} 创建失败
|
|
*/
|
|
public static boolean createFile(String path) {
|
|
if (TextUtils.isEmpty(path)) {
|
|
ALog.e(TAG, "文件路径不能为null");
|
|
return false;
|
|
}
|
|
File file = new File(path);
|
|
if (file.getParentFile() == null || !file.getParentFile().exists()) {
|
|
ALog.d(TAG, "目标文件所在路径不存在,准备创建……");
|
|
if (!createDir(file.getParent())) {
|
|
ALog.d(TAG, "创建目录文件所在的目录失败!文件路径【" + path + "】");
|
|
}
|
|
}
|
|
// 创建目标文件
|
|
if (file.exists()) {
|
|
final File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
|
|
if (file.renameTo(to)) {
|
|
to.delete();
|
|
} else {
|
|
file.delete();
|
|
}
|
|
}
|
|
try {
|
|
if (file.createNewFile()) {
|
|
ALog.d(TAG, "创建文件成功:" + file.getAbsolutePath());
|
|
return true;
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 通过文件名获取下载配置文件路径
|
|
*
|
|
* @param fileName 文件名
|
|
*/
|
|
public static String getFileConfigPath(boolean isDownload, String fileName) {
|
|
return AriaManager.APP.getFilesDir().getPath() + (isDownload ? AriaManager.DOWNLOAD_TEMP_DIR
|
|
: AriaManager.UPLOAD_TEMP_DIR) + fileName + ".properties";
|
|
}
|
|
|
|
/**
|
|
* 修改任务路径,修改文件路径和任务记录信息。如果是分块任务,则修改分块文件的路径。
|
|
*
|
|
* @param oldPath 旧的文件路径
|
|
* @param newPath 新的文件路径
|
|
*/
|
|
public static void modifyTaskRecord(String oldPath, String newPath) {
|
|
if (oldPath.equals(newPath)) {
|
|
ALog.w(TAG, "修改任务记录失败,新文件路径和旧文件路径一致");
|
|
return;
|
|
}
|
|
TaskRecord record = DbDataHelper.getTaskRecord(oldPath);
|
|
if (record == null) {
|
|
if (new File(oldPath).exists()) {
|
|
ALog.w(TAG, "修改任务记录失败,文件【" + oldPath + "】对应的任务记录不存在");
|
|
}
|
|
return;
|
|
}
|
|
if (!record.isBlock) {
|
|
File oldFile = new File(oldPath);
|
|
if (oldFile.exists()) {
|
|
oldFile.renameTo(new File(newPath));
|
|
}
|
|
}
|
|
|
|
record.filePath = newPath;
|
|
record.update();
|
|
// 修改线程记录
|
|
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
|
|
for (ThreadRecord tr : record.threadRecords) {
|
|
tr.key = newPath;
|
|
File blockFile = new File(String.format(AbsFileer.SUB_PATH, oldPath, tr.threadId));
|
|
if (blockFile.exists()) {
|
|
blockFile.renameTo(new File(String.format(AbsFileer.SUB_PATH, newPath, tr.threadId)));
|
|
}
|
|
}
|
|
DbEntity.saveAll(record.threadRecords);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 读取下载配置文件
|
|
*/
|
|
public static Properties loadConfig(File file) {
|
|
Properties properties = new Properties();
|
|
FileInputStream fis = null;
|
|
if (!file.exists()) {
|
|
createFile(file.getPath());
|
|
}
|
|
try {
|
|
fis = new FileInputStream(file);
|
|
properties.load(fis);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
try {
|
|
if (fis != null) {
|
|
fis.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
return properties;
|
|
}
|
|
|
|
/**
|
|
* 保存配置文件
|
|
*/
|
|
public static void saveConfig(File file, Properties properties) {
|
|
FileOutputStream fos = null;
|
|
try {
|
|
fos = new FileOutputStream(file, false);
|
|
properties.store(fos, null);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
try {
|
|
if (fos != null) {
|
|
fos.flush();
|
|
fos.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
} |