diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java index d4cdf491..844c2934 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java @@ -16,6 +16,7 @@ package com.arialyy.aria.core.common; import android.content.Context; +import android.os.Looper; import android.util.SparseArray; import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.download.BaseDListener; diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java index 87127e4b..ea68c1ab 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java @@ -58,22 +58,19 @@ public abstract class AbsThreadTask mConfig; - protected ENTITY mEntity; - protected TASK_WRAPPER mTaskWrapper; + private StateConstance sState; + private SubThreadConfig sConfig; + private ENTITY mEntity; + private TASK_WRAPPER mTaskWrapper; private int mFailTimes = 0; private long mLastSaveTime; private ExecutorService mConfigThreadPool; - protected boolean isNotNetRetry; //断网情况是否重试 + private boolean isNotNetRetry; //断网情况是否重试 private boolean taskBreak = false; //任务跳出 - protected int mThreadNum; - /** - * 速度限制工具 - */ - protected BandwidthLimiter mSpeedBandUtil; + private int mThreadNum; + protected BandwidthLimiter mSpeedBandUtil; //速度限制工具 protected AriaManager mAridManager; - protected boolean isInterrupted = false; + private boolean isInterrupted = false; private Thread mConfigThread = new Thread(new Runnable() { @Override public void run() { @@ -85,14 +82,14 @@ public abstract class AbsThreadTask config) { - STATE = constance; + sState = constance; + sConfig = config; mListener = listener; - mConfig = config; - mTaskWrapper = mConfig.TASK_WRAPPER; + mTaskWrapper = getConfig().TASK_WRAPPER; mEntity = mTaskWrapper.getEntity(); mLastSaveTime = System.currentTimeMillis(); mConfigThreadPool = Executors.newCachedThreadPool(); - mThreadNum = STATE.TASK_RECORD.threadRecords.size(); + mThreadNum = getState().TASK_RECORD.threadRecords.size(); mAridManager = AriaManager.getInstance(AriaManager.APP); if (getMaxSpeed() > 0) { mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), mThreadNum); @@ -122,21 +119,42 @@ protected boolean isLive() { * 当前线程是否完成,对于不支持断点的任务,一律未完成 {@code true} 完成;{@code false} 未完成 */ boolean isThreadComplete() { - return mConfig.THREAD_RECORD.isComplete; + return getConfig().THREAD_RECORD.isComplete; + } + + /** + * 获取状态信息 + */ + protected StateConstance getState() { + return sState; + } + + /** + * 获取实体 + */ + protected ENTITY getEntity() { + return mEntity; + } + + /** + * 获取任务驱动对象 + */ + protected TASK_WRAPPER getTaskWrapper() { + return mTaskWrapper; } /** * 获取任务记录 */ private TaskRecord getTaskRecord() { - return STATE.TASK_RECORD; + return getState().TASK_RECORD; } /** * 获取线程记录 */ protected ThreadRecord getThreadRecord() { - return mConfig.THREAD_RECORD; + return getConfig().THREAD_RECORD; } /** @@ -170,19 +188,19 @@ public void setMaxSpeed(int speed) { void breakTask() { synchronized (AriaManager.LOCK) { taskBreak = true; - if (mConfig.SUPPORT_BP) { + if (getConfig().SUPPORT_BP) { final long currentTemp = mChildCurrentLocation; - STATE.STOP_NUM++; - ALog.d(TAG, String.format("任务【%s】thread__%s__中断【停止位置:%s】", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID, currentTemp)); + getState().STOP_NUM++; + ALog.d(TAG, String.format("任务【%s】thread__%s__中断【停止位置:%s】", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID, currentTemp)); writeConfig(false, currentTemp); - if (STATE.isStop()) { - ALog.i(TAG, String.format("任务【%s】已中断", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; + if (getState().isStop()) { + ALog.i(TAG, String.format("任务【%s】已中断", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; } } else { - ALog.i(TAG, String.format("任务【%s】已中断", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; + ALog.i(TAG, String.format("任务【%s】已中断", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; } } } @@ -193,7 +211,7 @@ void breakTask() { * @return {@code true}正在运行 */ public boolean isRunning() { - return STATE.isRunning; + return getState().isRunning; } public boolean isInterrupted() { @@ -203,8 +221,8 @@ public boolean isInterrupted() { /** * 获取线程配置信息 */ - public SubThreadConfig getConfig() { - return mConfig; + protected SubThreadConfig getConfig() { + return sConfig; } @Override protected void finalize() throws Throwable { @@ -220,7 +238,7 @@ public SubThreadConfig getConfig() { * @return {@code true} 中断,{@code false} 不是中断 */ protected boolean isBreak() { - return STATE.isCancel || STATE.isStop || taskBreak; + return getState().isCancel || getState().isStop || taskBreak; } /** @@ -230,10 +248,10 @@ protected boolean isBreak() { */ protected boolean mergeFile() { List partPath = new ArrayList<>(); - for (int i = 0, len = STATE.TASK_RECORD.threadNum; i < len; i++) { - partPath.add(String.format(AbsFileer.SUB_PATH, STATE.TASK_RECORD.filePath, i)); + for (int i = 0, len = getState().TASK_RECORD.threadNum; i < len; i++) { + partPath.add(String.format(AbsFileer.SUB_PATH, getState().TASK_RECORD.filePath, i)); } - boolean isSuccess = FileUtil.mergeFile(STATE.TASK_RECORD.filePath, partPath); + boolean isSuccess = FileUtil.mergeFile(getState().TASK_RECORD.filePath, partPath); if (isSuccess) { for (String pp : partPath) { File f = new File(pp); @@ -241,10 +259,10 @@ protected boolean mergeFile() { f.delete(); } } - File targetFile = new File(STATE.TASK_RECORD.filePath); - if (targetFile.exists() && targetFile.length() > mEntity.getFileSize()) { + File targetFile = new File(getState().TASK_RECORD.filePath); + if (targetFile.exists() && targetFile.length() > getEntity().getFileSize()) { ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s", - mConfig.TEMP_FILE.getName(), targetFile.length(), mEntity.getFileSize())); + getConfig().TEMP_FILE.getName(), targetFile.length(), getEntity().getFileSize())); return false; } return true; @@ -282,30 +300,31 @@ protected boolean checkBlock() { */ public void stop() { synchronized (AriaManager.LOCK) { - if (mConfig.SUPPORT_BP) { + if (getConfig().SUPPORT_BP) { final long stopLocation; if (getTaskRecord().isBlock) { File blockFile = getBockFile(); ThreadRecord tr = getThreadRecord(); - long block = mEntity.getFileSize() / getTaskRecord().threadRecords.size(); + long block = getEntity().getFileSize() / getTaskRecord().threadRecords.size(); stopLocation = blockFile.exists() ? (tr.threadId * block + blockFile.length()) : tr.threadId * block; } else { stopLocation = mChildCurrentLocation; } - STATE.STOP_NUM++; - ALog.d(TAG, String.format("任务【%s】thread__%s__停止【当前线程停止位置:%s】", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID, stopLocation)); + getState().STOP_NUM++; + ALog.d(TAG, + String.format("任务【%s】thread__%s__停止【当前线程停止位置:%s】", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID, stopLocation)); writeConfig(false, stopLocation); - if (STATE.isStop()) { - ALog.i(TAG, String.format("任务【%s】已停止", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; - mListener.onStop(STATE.CURRENT_LOCATION); + if (getState().isStop()) { + ALog.i(TAG, String.format("任务【%s】已停止", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; + mListener.onStop(getState().CURRENT_LOCATION); } } else { - ALog.i(TAG, String.format("任务【%s】已停止", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; - mListener.onStop(STATE.CURRENT_LOCATION); + ALog.i(TAG, String.format("任务【%s】已停止", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; + mListener.onStop(getState().CURRENT_LOCATION); } } } @@ -315,19 +334,20 @@ public void stop() { */ protected void progress(long len) { synchronized (AriaManager.LOCK) { - if (STATE.CURRENT_LOCATION > mEntity.getFileSize() && !mTaskWrapper.asHttp().isChunked()) { + if (getState().CURRENT_LOCATION > getEntity().getFileSize() && !getTaskWrapper().asHttp() + .isChunked()) { String errorMsg = String.format("下载失败,下载长度超出文件真实长度;currentLocation=%s, fileSize=%s", - STATE.CURRENT_LOCATION, - mEntity.getFileSize()); + getState().CURRENT_LOCATION, + getEntity().getFileSize()); taskBreak = true; fail(mChildCurrentLocation, new FileException(TAG, errorMsg), false); return; } mChildCurrentLocation += len; - STATE.CURRENT_LOCATION += len; + getState().CURRENT_LOCATION += len; if (System.currentTimeMillis() - mLastSaveTime > 5000 - && mChildCurrentLocation < mConfig.END_LOCATION) { + && mChildCurrentLocation < getConfig().END_LOCATION) { mLastSaveTime = System.currentTimeMillis(); if (!mConfigThreadPool.isShutdown()) { mConfigThreadPool.execute(mConfigThread); @@ -341,21 +361,22 @@ protected void progress(long len) { */ public void cancel() { synchronized (AriaManager.LOCK) { - if (mConfig.SUPPORT_BP) { - STATE.CANCEL_NUM++; + if (getConfig().SUPPORT_BP) { + getState().CANCEL_NUM++; ALog.d(TAG, - String.format("任务【%s】thread__%s__取消", mConfig.TEMP_FILE.getName(), mConfig.THREAD_ID)); - if (STATE.isCancel()) { - if (mConfig.TEMP_FILE.exists() && !(mEntity instanceof UploadEntity)) { - mConfig.TEMP_FILE.delete(); + String.format("任务【%s】thread__%s__取消", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID)); + if (getState().isCancel()) { + if (getConfig().TEMP_FILE.exists() && !(getEntity() instanceof UploadEntity)) { + getConfig().TEMP_FILE.delete(); } - ALog.d(TAG, String.format("任务【%s】已取消", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; + ALog.d(TAG, String.format("任务【%s】已取消", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; mListener.onCancel(); } } else { - ALog.d(TAG, String.format("任务【%s】已取消", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; + ALog.d(TAG, String.format("任务【%s】已取消", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; mListener.onCancel(); } } @@ -377,18 +398,16 @@ protected void fail(final long subCurrentLocation, BaseException ex) { * @param subCurrentLocation 当前子线程进度 */ protected void fail(final long subCurrentLocation, BaseException ex, boolean needRetry) { - synchronized (AriaManager.LOCK) { - if (ex != null) { - ALog.e(TAG, ALog.getExceptionString(ex)); - } - if (mConfig.SUPPORT_BP) { - writeConfig(false, subCurrentLocation); - retryThis(needRetry && STATE.START_THREAD_NUM != 1); - } else { - ALog.e(TAG, String.format("任务【%s】执行失败", mConfig.TEMP_FILE.getName())); - ErrorHelp.saveError(TAG, "", ALog.getExceptionString(ex)); - handleFailState(!isBreak()); - } + if (ex != null) { + ALog.e(TAG, ALog.getExceptionString(ex)); + } + if (getConfig().SUPPORT_BP) { + writeConfig(false, subCurrentLocation); + retryThis(needRetry && getState().START_THREAD_NUM != 1); + } else { + ALog.e(TAG, String.format("任务【%s】执行失败", getConfig().TEMP_FILE.getName())); + ErrorHelp.saveError(TAG, "", ALog.getExceptionString(ex)); + handleFailState(!isBreak()); } } @@ -398,22 +417,20 @@ protected void fail(final long subCurrentLocation, BaseException ex, boolean nee * @param needRetry 是否可以重试 */ private void retryThis(boolean needRetry) { - synchronized (AriaManager.LOCK) { - if (!NetUtils.isConnected(AriaManager.APP) && !isNotNetRetry) { - ALog.w(TAG, String.format("任务【%s】thread__%s__重试失败,网络未连接", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID)); - } - if (mFailTimes < RETRY_NUM && needRetry && (NetUtils.isConnected(AriaManager.APP) - || isNotNetRetry) && !isBreak()) { - ALog.w(TAG, - String.format("任务【%s】thread__%s__正在重试", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID)); - mFailTimes++; - handleRetryRecord(); - ThreadTaskManager.getInstance().retryThread(AbsThreadTask.this); - } else { - handleFailState(!isBreak()); - } + if (!NetUtils.isConnected(AriaManager.APP) && !isNotNetRetry) { + ALog.w(TAG, String.format("任务【%s】thread__%s__重试失败,网络未连接", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID)); + } + if (mFailTimes < RETRY_NUM && needRetry && (NetUtils.isConnected(AriaManager.APP) + || isNotNetRetry) && !isBreak()) { + ALog.w(TAG, + String.format("任务【%s】thread__%s__正在重试", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID)); + mFailTimes++; + handleRetryRecord(); + ThreadTaskManager.getInstance().retryThread(AbsThreadTask.this); + } else { + handleFailState(!isBreak()); } } @@ -423,7 +440,7 @@ private void retryThis(boolean needRetry) { private void handleRetryRecord() { if (getTaskRecord().isBlock) { ThreadRecord tr = getThreadRecord(); - long block = mEntity.getFileSize() / getTaskRecord().threadRecords.size(); + long block = getEntity().getFileSize() / getTaskRecord().threadRecords.size(); File blockFile = getBockFile(); if (blockFile.length() > tr.blockLen) { @@ -431,22 +448,22 @@ private void handleRetryRecord() { blockFile.delete(); tr.startLocation = block * tr.threadId; tr.isComplete = false; - mConfig.START_LOCATION = tr.startLocation; + getConfig().START_LOCATION = tr.startLocation; } else if (blockFile.length() < tr.blockLen) { tr.startLocation = block * tr.threadId + blockFile.length(); tr.isComplete = false; - mConfig.START_LOCATION = tr.startLocation; - STATE.CURRENT_LOCATION = getBlockRealTotalSize(); + getConfig().START_LOCATION = tr.startLocation; + getState().CURRENT_LOCATION = getBlockRealTotalSize(); ALog.i(TAG, String.format("修正分块【%s】,开始位置:%s,当前进度:%s", blockFile.getPath(), tr.startLocation, - STATE.CURRENT_LOCATION)); + getState().CURRENT_LOCATION)); } else { - STATE.COMPLETE_THREAD_NUM++; + getState().COMPLETE_THREAD_NUM++; tr.isComplete = true; } tr.update(); } else { - mConfig.START_LOCATION = mChildCurrentLocation == 0 ? mConfig.START_LOCATION - : mConfig.THREAD_RECORD.startLocation; + getConfig().START_LOCATION = mChildCurrentLocation == 0 ? getConfig().START_LOCATION + : getConfig().THREAD_RECORD.startLocation; } } @@ -456,7 +473,7 @@ private void handleRetryRecord() { * @return 分块文件 */ private File getBockFile() { - return new File(String.format(AbsFileer.SUB_PATH, STATE.TASK_RECORD.filePath, + return new File(String.format(AbsFileer.SUB_PATH, getState().TASK_RECORD.filePath, getThreadRecord().threadId)); } @@ -482,15 +499,13 @@ private long getBlockRealTotalSize() { * @param taskNeedReTry 任务是否需要重试{@code true} 需要 */ private void handleFailState(boolean taskNeedReTry) { - synchronized (AriaManager.LOCK) { - STATE.FAIL_NUM++; - if (STATE.isFail()) { - STATE.isRunning = false; - // 手动停止不进行fail回调 - if (!STATE.isStop) { - String errorMsg = String.format("任务【%s】执行失败", mConfig.TEMP_FILE.getName()); - mListener.onFail(taskNeedReTry, new TaskException(TAG, errorMsg)); - } + getState().FAIL_NUM++; + if (getState().isFail()) { + getState().isRunning = false; + // 手动停止不进行fail回调 + if (!getState().isStop) { + String errorMsg = String.format("任务【%s】执行失败", getConfig().TEMP_FILE.getName()); + mListener.onFail(taskNeedReTry, new TaskException(TAG, errorMsg)); } } } @@ -502,21 +517,19 @@ private void handleFailState(boolean taskNeedReTry) { * @param record 当前下载进度 */ protected void writeConfig(boolean isComplete, final long record) { - synchronized (AriaManager.LOCK) { - ThreadRecord tr = getThreadRecord(); - if (tr != null) { - tr.isComplete = isComplete; - if (getTaskRecord().isBlock) { + ThreadRecord tr = getThreadRecord(); + if (tr != null) { + tr.isComplete = isComplete; + if (getTaskRecord().isBlock) { + tr.startLocation = record; + } else if (getTaskRecord().isOpenDynamicFile) { + tr.startLocation = getConfig().TEMP_FILE.length(); + } else { + if (0 < record && record < getConfig().END_LOCATION) { tr.startLocation = record; - } else if (getTaskRecord().isOpenDynamicFile) { - tr.startLocation = mConfig.TEMP_FILE.length(); - } else { - if (0 < record && record < mConfig.END_LOCATION) { - tr.startLocation = record; - } } - tr.update(); } + tr.update(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java b/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java index 82ed853f..7b8b34c0 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java @@ -15,6 +15,7 @@ */ package com.arialyy.aria.core.common; +import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.exception.BaseException; public interface OnFileInfoCallback { @@ -30,5 +31,5 @@ public interface OnFileInfoCallback { * * @param e 错误信息 */ - void onFail(String url, BaseException e, boolean needRetry); + void onFail(AbsEntity entity, BaseException e, boolean needRetry); } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java index bc9ff75f..4b1533e7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java @@ -374,7 +374,7 @@ protected void handleFile(String remotePath, FTPFile ftpFile) { private void failDownload(BaseException e, boolean needRetry) { if (mCallback != null) { - mCallback.onFail(mEntity.getKey(), e, needRetry); + mCallback.onFail(mEntity, e, needRetry); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java index 9e949f95..d7b6be3f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java @@ -57,7 +57,7 @@ protected AbsFtpThreadTask(StateConstance constance, IEventListener listener, */ protected FTPClient createClient() { FTPClient client = null; - final FtpUrlEntity urlEntity = mTaskWrapper.asFtp().getUrlEntity(); + final FtpUrlEntity urlEntity = getTaskWrapper().asFtp().getUrlEntity(); if (urlEntity.validAddr == null) { try { InetAddress[] ips = InetAddress.getAllByName(urlEntity.hostName); @@ -106,8 +106,8 @@ protected FTPClient createClient() { // 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码 charSet = "UTF-8"; if (reply != FTPReply.COMMAND_IS_SUPERFLUOUS) { - if (!TextUtils.isEmpty(mTaskWrapper.asFtp().getCharSet())) { - charSet = mTaskWrapper.asFtp().getCharSet(); + if (!TextUtils.isEmpty(getTaskWrapper().asFtp().getCharSet())) { + charSet = getTaskWrapper().asFtp().getCharSet(); } } client.setControlEncoding(charSet); @@ -116,7 +116,7 @@ protected FTPClient createClient() { client.enterLocalPassiveMode(); client.setFileType(FTP.BINARY_FILE_TYPE); client.setControlKeepAliveTimeout(5000); - if (mTaskWrapper.asFtp().getUrlEntity().isFtps) { + if (getTaskWrapper().asFtp().getUrlEntity().isFtps) { ((FTPSClient) client).execPROT("P"); } } catch (java.io.IOException e) { @@ -150,7 +150,7 @@ private FTPClient newInstanceClient(FtpUrlEntity urlEntity) { private FTPClient connect(FTPClient client, InetAddress[] ips, int index, int port) { try { client.connect(ips[index], port); - mTaskWrapper.asFtp().getUrlEntity().validAddr = ips[index]; + getTaskWrapper().asFtp().getUrlEntity().validAddr = ips[index]; return client; } catch (java.io.IOException e) { try { diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java index 40ac1f48..20ba2357 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java @@ -1,104 +1,103 @@ -/* - * 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.core.common.http; - -import android.text.TextUtils; -import com.arialyy.aria.core.download.DGTaskWrapper; -import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.download.DownloadGroupTarget; -import com.arialyy.aria.core.inf.AbsTarget; -import com.arialyy.aria.core.inf.ITargetHandler; -import com.arialyy.aria.util.ALog; -import java.util.HashMap; -import java.util.Map; - -/** - * HTTP参数委托 - * @param - */ -class HttpDelegate implements ITargetHandler { - private static final String TAG = "PostDelegate"; - TARGET mTarget; - - HttpDelegate(TARGET target) { - mTarget = target; - } - - public TARGET setParams(Map params) { - mTarget.getTaskWrapper().asHttp().setParams(params); - if (mTarget instanceof DownloadGroupTarget) { - for (DTaskWrapper subTask : ((DGTaskWrapper) mTarget.getTaskWrapper()).getSubTaskWrapper()) { - subTask.asHttp().setParams(params); - } - } - return mTarget; - } - - public TARGET setParam(String key, String value) { - if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { - ALog.d(TAG, "key 或value 为空"); - return mTarget; - } - Map params = mTarget.getTaskWrapper().asHttp().getParams(); - if (params == null) { - params = new HashMap<>(); - mTarget.getTaskWrapper().asHttp().setParams(params); - } - params.put(key, value); - if (mTarget instanceof DownloadGroupTarget) { - for (DTaskWrapper subTask : ((DGTaskWrapper) mTarget.getTaskWrapper()).getSubTaskWrapper()) { - subTask.asHttp().setParams(params); - } - } - return mTarget; - } - - @Override public void add() { - mTarget.add(); - } - - @Override public void start() { - mTarget.start(); - } - - @Override public void stop() { - mTarget.stop(); - } - - @Override public void resume() { - mTarget.resume(); - } - - @Override public void cancel() { - mTarget.cancel(); - } - - @Override public void save() { - mTarget.save(); - } - - @Override public void cancel(boolean removeFile) { - mTarget.cancel(removeFile); - } - - @Override public void reTry() { - mTarget.reTry(); - } - - @Override public void reStart() { - mTarget.reStart(); - } -} +/* + * 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.core.common.http; + +import android.text.TextUtils; +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadGroupTarget; +import com.arialyy.aria.core.inf.AbsTarget; +import com.arialyy.aria.core.inf.ITargetHandler; +import com.arialyy.aria.util.ALog; +import java.util.HashMap; +import java.util.Map; + +/** + * HTTP参数委托 + */ +class HttpDelegate implements ITargetHandler { + private static final String TAG = "PostDelegate"; + TARGET mTarget; + + HttpDelegate(TARGET target) { + mTarget = target; + } + + public TARGET setParams(Map params) { + mTarget.getTaskWrapper().asHttp().setParams(params); + if (mTarget instanceof DownloadGroupTarget) { + for (DTaskWrapper subTask : ((DGTaskWrapper) mTarget.getTaskWrapper()).getSubTaskWrapper()) { + subTask.asHttp().setParams(params); + } + } + return mTarget; + } + + public TARGET setParam(String key, String value) { + if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { + ALog.d(TAG, "key 或value 为空"); + return mTarget; + } + Map params = mTarget.getTaskWrapper().asHttp().getParams(); + if (params == null) { + params = new HashMap<>(); + mTarget.getTaskWrapper().asHttp().setParams(params); + } + params.put(key, value); + if (mTarget instanceof DownloadGroupTarget) { + for (DTaskWrapper subTask : ((DGTaskWrapper) mTarget.getTaskWrapper()).getSubTaskWrapper()) { + subTask.asHttp().setParams(params); + } + } + return mTarget; + } + + @Override public void add() { + mTarget.add(); + } + + @Override public void start() { + mTarget.start(); + } + + @Override public void stop() { + mTarget.stop(); + } + + @Override public void resume() { + mTarget.resume(); + } + + @Override public void cancel() { + mTarget.cancel(); + } + + @Override public void save() { + mTarget.save(); + } + + @Override public void cancel(boolean removeFile) { + mTarget.cancel(removeFile); + } + + @Override public void reTry() { + mTarget.reTry(); + } + + @Override public void reStart() { + mTarget.reStart(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java index 09bc976b..036be3da 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java @@ -17,10 +17,20 @@ import android.support.annotation.NonNull; import android.text.TextUtils; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.inf.AbsHttpFileLenAdapter; import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.core.inf.IHttpHeaderDelegate; +import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.io.ObjectInputStream; +import java.io.Serializable; import java.net.Proxy; import java.util.Collection; import java.util.Map; @@ -80,7 +90,31 @@ public TARGET addHeaders(@NonNull Map headers) { return mTarget; } - public void addHeader(AbsTaskWrapper taskWrapper, String key, String value) { + public TARGET setFileLenAdapter(AbsHttpFileLenAdapter adapter) { + if (adapter == null) { + throw new IllegalArgumentException("adapter为空"); + } + try { + adapter.clone(); + mTarget.getTaskWrapper().asHttp().setFileLenAdapter((IHttpFileLenAdapter) adapter.clone()); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + //// 以下代码有问题,匿名内部类不能序列化 + //String objPath = String.format("%s/obj_temp/%s", AriaManager.APP.getFilesDir().getPath(), + // adapter.hashCode()); + //CommonUtil.writeObjToFile(objPath, adapter); + //IHttpFileLenAdapter newAdapter = (IHttpFileLenAdapter) CommonUtil.readObjFromFile(objPath); + //File temp = new File(objPath); + //if (temp.exists()) { + // temp.delete(); + //} + //mTarget.getTaskWrapper().asHttp().setFileLenAdapter(newAdapter); + + return mTarget; + } + + private void addHeader(AbsTaskWrapper taskWrapper, String key, String value) { HttpTaskDelegate taskDelegate = taskWrapper.asHttp(); if (taskDelegate.getHeaders().get(key) == null) { taskDelegate.getHeaders().put(key, value); @@ -89,7 +123,7 @@ public void addHeader(AbsTaskWrapper taskWrapper, String key, String value) { } } - public void addHeaders(AbsTaskWrapper taskWrapper, Map headers) { + private void addHeaders(AbsTaskWrapper taskWrapper, Map headers) { HttpTaskDelegate taskDelegate = taskWrapper.asHttp(); /* 两个map比较逻辑 diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java index 5dadd800..f5ee86e7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java @@ -1,187 +1,198 @@ -/* - * 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.core.common.http; - -import com.arialyy.aria.core.common.RequestEnum; -import com.arialyy.aria.core.inf.ITargetHeadDelegate; -import java.net.CookieManager; -import java.net.Proxy; -import java.util.HashMap; -import java.util.Map; - -/** - * Http任务设置的信息,如:cookie、请求参数 - */ -public class HttpTaskDelegate implements ITargetHeadDelegate { - - private CookieManager cookieManager; - - /** - * 请求参数 - */ - private Map params; - - /** - * http 请求头 - */ - private Map headers = new HashMap<>(); - - /** - * 字符编码,默认为"utf-8" - */ - private String charSet = "utf-8"; - - /** - * 网络请求类型 - */ - private RequestEnum requestEnum = RequestEnum.GET; - - /** - * 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment; filename="filename.jpg"} {@code true} - * 使用 - */ - private boolean useServerFileName = false; - - /** - * 重定向链接 - */ - private String redirectUrl = ""; - - /** - * 是否是chunk模式 - */ - private boolean isChunked = false; - /** - * 文件上传需要的key - */ - private String attachment; - /** - * 上传的文件类型 - */ - private String contentType = "multipart/form-data"; - private String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"; - - private Proxy proxy; - /** - * 文件上传表单 - */ - private Map formFields = new HashMap<>(); - - public Map getFormFields() { - return formFields; - } - - public void setFormFields(Map formFields) { - this.formFields = formFields; - } - - public String getAttachment() { - return attachment; - } - - public void setAttachment(String attachment) { - this.attachment = attachment; - } - - public String getContentType() { - return contentType; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public boolean isChunked() { - return isChunked; - } - - public void setChunked(boolean chunked) { - isChunked = chunked; - } - - public CookieManager getCookieManager() { - return cookieManager; - } - - public void setCookieManager(CookieManager cookieManager) { - this.cookieManager = cookieManager; - } - - public Proxy getProxy() { - return proxy; - } - - public void setProxy(Proxy proxy) { - this.proxy = proxy; - } - - public Map getHeaders() { - return headers; - } - - public void setHeaders(Map headers) { - this.headers = headers; - } - - public String getCharSet() { - return charSet; - } - - public void setCharSet(String charSet) { - this.charSet = charSet; - } - - public RequestEnum getRequestEnum() { - return requestEnum; - } - - public void setRequestEnum(RequestEnum requestEnum) { - this.requestEnum = requestEnum; - } - - public boolean isUseServerFileName() { - return useServerFileName; - } - - public void setUseServerFileName(boolean useServerFileName) { - this.useServerFileName = useServerFileName; - } - - public String getRedirectUrl() { - return redirectUrl; - } - - public void setRedirectUrl(String redirectUrl) { - this.redirectUrl = redirectUrl; - } - - public Map getParams() { - return params; - } - - public void setParams(Map params) { - this.params = params; - } -} +/* + * 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.core.common.http; + +import com.arialyy.aria.core.common.RequestEnum; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; +import com.arialyy.aria.core.inf.ITargetHeadDelegate; +import java.net.CookieManager; +import java.net.Proxy; +import java.util.HashMap; +import java.util.Map; + +/** + * Http任务设置的信息,如:cookie、请求参数 + */ +public class HttpTaskDelegate implements ITargetHeadDelegate { + + private CookieManager cookieManager; + + /** + * 请求参数 + */ + private Map params; + + /** + * http 请求头 + */ + private Map headers = new HashMap<>(); + + /** + * 字符编码,默认为"utf-8" + */ + private String charSet = "utf-8"; + + /** + * 网络请求类型 + */ + private RequestEnum requestEnum = RequestEnum.GET; + + /** + * 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment; filename="filename.jpg"} {@code true} + * 使用 + */ + private boolean useServerFileName = false; + + /** + * 重定向链接 + */ + private String redirectUrl = ""; + + /** + * 是否是chunk模式 + */ + private boolean isChunked = false; + /** + * 文件上传需要的key + */ + private String attachment; + /** + * 上传的文件类型 + */ + private String contentType = "multipart/form-data"; + private String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"; + + private Proxy proxy; + /** + * 文件上传表单 + */ + private Map formFields = new HashMap<>(); + + private IHttpFileLenAdapter fileLenAdapter; + + public IHttpFileLenAdapter getFileLenAdapter() { + return fileLenAdapter; + } + + public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) { + this.fileLenAdapter = fileLenAdapter; + } + + public Map getFormFields() { + return formFields; + } + + public void setFormFields(Map formFields) { + this.formFields = formFields; + } + + public String getAttachment() { + return attachment; + } + + public void setAttachment(String attachment) { + this.attachment = attachment; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } + + public boolean isChunked() { + return isChunked; + } + + public void setChunked(boolean chunked) { + isChunked = chunked; + } + + public CookieManager getCookieManager() { + return cookieManager; + } + + public void setCookieManager(CookieManager cookieManager) { + this.cookieManager = cookieManager; + } + + public Proxy getProxy() { + return proxy; + } + + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public String getCharSet() { + return charSet; + } + + public void setCharSet(String charSet) { + this.charSet = charSet; + } + + public RequestEnum getRequestEnum() { + return requestEnum; + } + + public void setRequestEnum(RequestEnum requestEnum) { + this.requestEnum = requestEnum; + } + + public boolean isUseServerFileName() { + return useServerFileName; + } + + public void setUseServerFileName(boolean useServerFileName) { + this.useServerFileName = useServerFileName; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/AbsDGTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/AbsDGTarget.java index c51871cc..44f35dce 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/AbsDGTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/AbsDGTarget.java @@ -1,171 +1,68 @@ -/* - * 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.core.download; - -import android.support.annotation.CheckResult; -import android.text.TextUtils; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.core.inf.AbsTarget; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.manager.SubTaskManager; -import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; -import com.arialyy.aria.orm.DbEntity; -import com.arialyy.aria.util.ALog; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by lyy on 2017/7/26. - */ -abstract class AbsDGTarget extends AbsTarget { - - ///** - // * 组任务名 - // */ - //String mGroupHash; - ///** - // * 文件夹临时路径 - // */ - //String mDirPathTemp; - ///** - // * 是否需要修改路径 - // */ - //boolean needModifyPath = false; - - private SubTaskManager mSubTaskManager; - - /** - * 获取子任务管理器 - * - * @return 子任务管理器 - */ - @CheckResult - public SubTaskManager getSubTaskManager() { - if (mSubTaskManager == null) { - mSubTaskManager = new SubTaskManager(getTargetName(), getTaskWrapper()); - } - return mSubTaskManager; - } - - @Override public DownloadGroupEntity getEntity() { - return (DownloadGroupEntity) super.getEntity(); - } - - @Override public DGTaskWrapper getTaskWrapper() { - return (DGTaskWrapper) super.getTaskWrapper(); - } - - /** - * 设置任务组别名 - */ - @CheckResult - public TARGET setGroupAlias(String alias) { - if (TextUtils.isEmpty(alias)) return (TARGET) this; - getEntity().setAlias(alias); - return (TARGET) this; - } - - //@Override public boolean taskExists() { - // return DbEntity.checkDataExist(DownloadGroupEntity.class, "groupHash=?", mGroupHash); - //} - - ///** - // * 设置任务组的文件夹路径,在Aria中,任务组的所有子任务都会下载到以任务组组名的文件夹中。 - // * 如:groupDirPath = "/mnt/sdcard/download/group_test" - // *
-  // *   {@code
-  // *      + mnt
-  // *        + sdcard
-  // *          + download
-  // *            + group_test
-  // *              - task1.apk
-  // *              - task2.apk
-  // *              - task3.apk
-  // *              ....
-  // *
-  // *   }
-  // * 
- // * - // * @param dirPath 任务组保存文件夹路径 - // */ - //@CheckResult - //public TARGET setDirPath(String dirPath) { - // mDirPathTemp = dirPath; - // return (TARGET) this; - //} - - //@Override public boolean isRunning() { - // DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(getEntity().getKey()); - // return task != null && task.isRunning(); - //} - - ///** - // * 改变任务组文件夹路径,修改文件夹路径会将子任务所有路径更换 - // * - // * @param newDirPath 新的文件夹路径 - // */ - //void reChangeDirPath(String newDirPath) { - // List subTasks = getTaskWrapper().getSubTaskWrapper(); - // if (subTasks != null && !subTasks.isEmpty()) { - // List des = new ArrayList<>(); - // for (DTaskWrapper dte : subTasks) { - // DownloadEntity de = dte.getEntity(); - // String oldPath = de.getDownloadPath(); - // String newPath = newDirPath + "/" + de.getFileName(); - // File file = new File(oldPath); - // if (file.exists()) { - // file.renameTo(new File(newPath)); - // } - // de.setDownloadPath(newPath); - // des.add(de); - // } - // AbsEntity.saveAll(des); - // } - //} - - ///** - // * 检查并设置文件夹路径 - // * - // * @return {@code true} 合法 - // */ - //boolean checkDirPath() { - // if (TextUtils.isEmpty(mDirPathTemp)) { - // ALog.e(TAG, "文件夹路径不能为null"); - // return false; - // } else if (!mDirPathTemp.startsWith("/")) { - // ALog.e(TAG, "文件夹路径【" + mDirPathTemp + "】错误"); - // return false; - // } - // File file = new File(mDirPathTemp); - // if (file.isFile()) { - // ALog.e(TAG, "路径【" + mDirPathTemp + "】是文件,请设置文件夹路径"); - // return false; - // } - // - // if (TextUtils.isEmpty(getEntity().getDirPath()) || !getEntity().getDirPath() - // .equals(mDirPathTemp)) { - // if (!file.exists()) { - // file.mkdirs(); - // } - // needModifyPath = true; - // getEntity().setDirPath(mDirPathTemp); - // ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", mDirPathTemp)); - // } - // - // return true; - //} -} +/* + * 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.core.download; + +import android.support.annotation.CheckResult; +import android.text.TextUtils; +import com.arialyy.aria.core.inf.AbsEntity; +import com.arialyy.aria.core.inf.AbsTarget; +import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.manager.SubTaskManager; +import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; +import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.ALog; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by lyy on 2017/7/26. + */ +abstract class AbsDGTarget extends AbsTarget { + + private SubTaskManager mSubTaskManager; + + /** + * 获取子任务管理器 + * + * @return 子任务管理器 + */ + @CheckResult + public SubTaskManager getSubTaskManager() { + if (mSubTaskManager == null) { + mSubTaskManager = new SubTaskManager(getTargetName(), getTaskWrapper()); + } + return mSubTaskManager; + } + + @Override public DownloadGroupEntity getEntity() { + return (DownloadGroupEntity) super.getEntity(); + } + + @Override public DGTaskWrapper getTaskWrapper() { + return (DGTaskWrapper) super.getTaskWrapper(); + } + + /** + * 设置任务组别名 + */ + @CheckResult + public TARGET setGroupAlias(String alias) { + if (TextUtils.isEmpty(alias)) return (TARGET) this; + getEntity().setAlias(alias); + return (TARGET) this; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java index 2976704a..30fb6bd3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java @@ -134,7 +134,7 @@ protected void saveData(int state, long location) { mTaskWrapper.setState(state); mEntity.setState(state); if (state == IEntity.STATE_CANCEL) { - CommonUtil.delGroupTaskRecord(mTaskWrapper.isRemoveFile(), mEntity); + CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile()); return; } else if (state == IEntity.STATE_STOP) { mEntity.setStopTime(System.currentTimeMillis()); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java index 834b5372..0d1b3a82 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java @@ -19,6 +19,8 @@ import android.support.annotation.NonNull; import com.arialyy.aria.core.common.http.HttpHeaderDelegate; import com.arialyy.aria.core.common.http.PostDelegate; +import com.arialyy.aria.core.inf.AbsHttpFileLenAdapter; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.core.inf.IHttpHeaderDelegate; import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.exception.ParamException; @@ -157,6 +159,13 @@ public DownloadGroupTarget setSubFileName(List subTaskFileName) { return mGroupDelegate.setSubFileName(subTaskFileName); } + /** + * 如果你需要使用header中特定的key来设置文件长度,或有定制文件长度的需要,那么你可以通过该方法自行处理文件长度 + */ + public DownloadGroupTarget setFileLenAdapter(AbsHttpFileLenAdapter adapter) { + return mHeaderDelegate.setFileLenAdapter(adapter); + } + @Override public int getTargetType() { return GROUP_HTTP; } @@ -175,17 +184,11 @@ public DownloadGroupTarget setSubFileName(List subTaskFileName) { @CheckResult @Override public DownloadGroupTarget addHeader(@NonNull String key, @NonNull String value) { - for (DTaskWrapper subTask : getTaskWrapper().getSubTaskWrapper()) { - mHeaderDelegate.addHeader(subTask, key, value); - } return mHeaderDelegate.addHeader(key, value); } @CheckResult @Override public DownloadGroupTarget addHeaders(Map headers) { - for (DTaskWrapper subTask : getTaskWrapper().getSubTaskWrapper()) { - mHeaderDelegate.addHeaders(subTask, headers); - } return mHeaderDelegate.addHeaders(headers); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java index eca02c0d..76af4ad4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java @@ -1,147 +1,156 @@ -/* - * 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.core.download; - -import android.support.annotation.CheckResult; -import android.support.annotation.NonNull; -import com.arialyy.aria.core.common.http.GetDelegate; -import com.arialyy.aria.core.common.http.HttpHeaderDelegate; -import com.arialyy.aria.core.common.http.PostDelegate; -import com.arialyy.aria.core.inf.IHttpHeaderDelegate; -import java.net.Proxy; -import java.util.Map; - -/** - * Created by lyy on 2016/12/5. - * https://github.com/AriaLyy/Aria - */ -public class DownloadTarget extends AbsDTarget - implements IHttpHeaderDelegate { - private HttpHeaderDelegate mHeaderDelegate; - private DNormalDelegate mNormalDelegate; - - DownloadTarget(DownloadEntity entity, String targetName) { - this(entity.getUrl(), targetName); - } - - DownloadTarget(String url, String targetName) { - mNormalDelegate = new DNormalDelegate<>(this, url, targetName); - mHeaderDelegate = new HttpHeaderDelegate<>(this); - } - - /** - * Post处理 - */ - @CheckResult - public PostDelegate asPost() { - return new PostDelegate<>(this); - } - - /** - * get参数传递 - */ - @CheckResult - public GetDelegate asGet() { - return new GetDelegate<>(this); - } - - /** - * 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment;filename=***} - * 如果获取不到服务器文件名,则使用用户设置的文件名 - * - * @param use {@code true} 使用 - */ - @CheckResult - public DownloadTarget useServerFileName(boolean use) { - getTaskWrapper().asHttp().setUseServerFileName(use); - return this; - } - - /** - * 设置文件存储路径,如果需要修改新的文件名,修改路径便可。 - * 如:原文件路径 /mnt/sdcard/test.zip - * 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip - * - * @param filePath 路径必须为文件路径,不能为文件夹路径 - */ - @CheckResult - public DownloadTarget setFilePath(@NonNull String filePath) { - mNormalDelegate.setTempFilePath(filePath); - return this; - } - - /** - * 设置文件存储路径,如果需要修改新的文件名,修改路径便可。 - * 如:原文件路径 /mnt/sdcard/test.zip - * 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip - * - * @param filePath 路径必须为文件路径,不能为文件夹路径 - * @param forceDownload {@code true}强制下载,不考虑文件路径是否被占用 - */ - @CheckResult - public DownloadTarget setFilePath(@NonNull String filePath, boolean forceDownload) { - mNormalDelegate.setTempFilePath(filePath); - mNormalDelegate.setForceDownload(forceDownload); - return this; - } - - /** - * 从header中获取文件描述信息 - */ - public String getContentDisposition() { - return getEntity().getDisposition(); - } - - @Override public DownloadTarget updateUrl(String newUrl) { - return mNormalDelegate.updateUrl(newUrl); - } - - @Override public int getTargetType() { - return HTTP; - } - - /** - * 设置URL的代理 - * - * @param proxy {@link Proxy} - */ - @CheckResult - @Override public DownloadTarget setUrlProxy(Proxy proxy) { - return mHeaderDelegate.setUrlProxy(proxy); - } - - @CheckResult - @Override public DownloadTarget addHeader(@NonNull String key, @NonNull String value) { - return mHeaderDelegate.addHeader(key, value); - } - - @CheckResult - @Override public DownloadTarget addHeaders(Map headers) { - return mHeaderDelegate.addHeaders(headers); - } - - @Override protected boolean checkEntity() { - return mNormalDelegate.checkEntity(); - } - - @Override public boolean isRunning() { - return mNormalDelegate.isRunning(); - } - - @Override public boolean taskExists() { - return mNormalDelegate.taskExists(); - } -} +/* + * 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.core.download; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import com.arialyy.aria.core.common.http.GetDelegate; +import com.arialyy.aria.core.common.http.HttpHeaderDelegate; +import com.arialyy.aria.core.common.http.PostDelegate; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; +import com.arialyy.aria.core.inf.IHttpHeaderDelegate; +import java.net.Proxy; +import java.util.Map; + +/** + * Created by lyy on 2016/12/5. + * https://github.com/AriaLyy/Aria + */ +public class DownloadTarget extends AbsDTarget + implements IHttpHeaderDelegate { + private HttpHeaderDelegate mHeaderDelegate; + private DNormalDelegate mNormalDelegate; + + DownloadTarget(DownloadEntity entity, String targetName) { + this(entity.getUrl(), targetName); + } + + DownloadTarget(String url, String targetName) { + mNormalDelegate = new DNormalDelegate<>(this, url, targetName); + mHeaderDelegate = new HttpHeaderDelegate<>(this); + } + + /** + * Post处理 + */ + @CheckResult + public PostDelegate asPost() { + return new PostDelegate<>(this); + } + + /** + * get参数传递 + */ + @CheckResult + public GetDelegate asGet() { + return new GetDelegate<>(this); + } + + /** + * 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment;filename=***} + * 如果获取不到服务器文件名,则使用用户设置的文件名 + * + * @param use {@code true} 使用 + */ + @CheckResult + public DownloadTarget useServerFileName(boolean use) { + getTaskWrapper().asHttp().setUseServerFileName(use); + return this; + } + + /** + * 设置文件存储路径,如果需要修改新的文件名,修改路径便可。 + * 如:原文件路径 /mnt/sdcard/test.zip + * 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip + * + * @param filePath 路径必须为文件路径,不能为文件夹路径 + */ + @CheckResult + public DownloadTarget setFilePath(@NonNull String filePath) { + mNormalDelegate.setTempFilePath(filePath); + return this; + } + + /** + * 设置文件存储路径,如果需要修改新的文件名,修改路径便可。 + * 如:原文件路径 /mnt/sdcard/test.zip + * 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip + * + * @param filePath 路径必须为文件路径,不能为文件夹路径 + * @param forceDownload {@code true}强制下载,不考虑文件路径是否被占用 + */ + @CheckResult + public DownloadTarget setFilePath(@NonNull String filePath, boolean forceDownload) { + mNormalDelegate.setTempFilePath(filePath); + mNormalDelegate.setForceDownload(forceDownload); + return this; + } + + /** + * 如果你需要使用header中特定的key来设置文件长度,或有定制文件长度的需要,那么你可以通过该方法自行处理文件长度 + */ + public DownloadTarget setFileLenAdapter(IHttpFileLenAdapter adapter) { + //return mHeaderDelegate.setFileLenAdapter(adapter); + return this; + } + + /** + * 从header中获取文件描述信息 + */ + public String getContentDisposition() { + return getEntity().getDisposition(); + } + + @Override public DownloadTarget updateUrl(String newUrl) { + return mNormalDelegate.updateUrl(newUrl); + } + + @Override public int getTargetType() { + return HTTP; + } + + /** + * 设置URL的代理 + * + * @param proxy {@link Proxy} + */ + @CheckResult + @Override public DownloadTarget setUrlProxy(Proxy proxy) { + return mHeaderDelegate.setUrlProxy(proxy); + } + + @CheckResult + @Override public DownloadTarget addHeader(@NonNull String key, @NonNull String value) { + return mHeaderDelegate.addHeader(key, value); + } + + @CheckResult + @Override public DownloadTarget addHeaders(Map headers) { + return mHeaderDelegate.addHeaders(headers); + } + + @Override protected boolean checkEntity() { + return mNormalDelegate.checkEntity(); + } + + @Override public boolean isRunning() { + return mNormalDelegate.isRunning(); + } + + @Override public boolean taskExists() { + return mNormalDelegate.taskExists(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java index 31fcf744..cbc8f18c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java @@ -38,8 +38,7 @@ class FtpFileInfoThread extends AbsFtpInfoThread { @Override protected void handleFile(String remotePath, FTPFile ftpFile) { super.handleFile(remotePath, ftpFile); if (!CommonUtil.checkSDMemorySpace(mEntity.getDownloadPath(), ftpFile.getSize())) { - mCallback.onFail(mEntity.getUrl(), - new AriaIOException(TAG, + mCallback.onFail(mEntity, new AriaIOException(TAG, String.format("获取ftp文件信息失败,内存空间不足, filePath: %s", mEntity.getDownloadPath())), false); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java index ecc33f3b..466f69f7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java @@ -48,31 +48,31 @@ class FtpThreadTask extends AbsFtpThreadTask { FtpThreadTask(StateConstance constance, IDownloadListener listener, SubThreadConfig downloadInfo) { super(constance, listener, downloadInfo); - isOpenDynamicFile = STATE.TASK_RECORD.isOpenDynamicFile; - isBlock = STATE.TASK_RECORD.isBlock; + isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile; + isBlock = getState().TASK_RECORD.isBlock; } @Override public FtpThreadTask call() throws Exception { super.call(); - if (mConfig.THREAD_RECORD.isComplete) { + if (getConfig().THREAD_RECORD.isComplete) { handleComplete(); return this; } - mChildCurrentLocation = mConfig.START_LOCATION; + mChildCurrentLocation = getConfig().START_LOCATION; FTPClient client = null; InputStream is = null; try { ALog.d(TAG, - String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID, mConfig.START_LOCATION, mConfig.END_LOCATION)); + String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); client = createClient(); if (client == null) { fail(mChildCurrentLocation, new TaskException(TAG, "ftp client 创建失败")); return this; } - if (mConfig.START_LOCATION > 0) { - client.setRestartOffset(mConfig.START_LOCATION); + if (getConfig().START_LOCATION > 0) { + client.setRestartOffset(getConfig().START_LOCATION); } //发送第二次指令时,还需要再做一次判断 int reply = client.getReplyCode(); @@ -84,7 +84,7 @@ class FtpThreadTask extends AbsFtpThreadTask { return this; } String remotePath = - new String(mTaskWrapper.asFtp().getUrlEntity().remotePath.getBytes(charSet), + new String(getTaskWrapper().asFtp().getUrlEntity().remotePath.getBytes(charSet), SERVER_CHARSET); ALog.i(TAG, String.format("remotePath【%s】", remotePath)); is = client.retrieveFileStream(remotePath); @@ -105,10 +105,10 @@ class FtpThreadTask extends AbsFtpThreadTask { } } catch (IOException e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", mConfig.URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); } catch (Exception e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", mConfig.URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); } finally { try { if (is != null) { @@ -135,28 +135,28 @@ private void handleComplete() { return; } ALog.i(TAG, - String.format("任务【%s】线程__%s__下载完毕", mConfig.TEMP_FILE.getName(), mConfig.THREAD_ID)); - writeConfig(true, mConfig.END_LOCATION); - STATE.COMPLETE_THREAD_NUM++; - if (STATE.isComplete()) { + String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(), getConfig().THREAD_ID)); + writeConfig(true, getConfig().END_LOCATION); + getState().COMPLETE_THREAD_NUM++; + if (getState().isComplete()) { if (isBlock) { boolean success = mergeFile(); if (!success) { - //ALog.e(TAG, String.format("任务【%s】分块文件合并失败", mConfig.TEMP_FILE.getName())); - STATE.isRunning = false; + //ALog.e(TAG, String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName())); + getState().isRunning = false; mListener.onFail(false, - new TaskException(TAG, String.format("任务【%s】分块文件合并失败", mConfig.TEMP_FILE.getName()))); + new TaskException(TAG, String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName()))); return; } } - STATE.TASK_RECORD.deleteData(); - STATE.isRunning = false; + getState().TASK_RECORD.deleteData(); + getState().isRunning = false; mListener.onComplete(); } - if (STATE.isFail()) { - STATE.isRunning = false; + if (getState().isFail()) { + getState().isRunning = false; mListener.onFail(false, - new TaskException(TAG, String.format("任务【%s】下载失败", mConfig.TEMP_FILE.getName()))); + new TaskException(TAG, String.format("任务【%s】下载失败", getConfig().TEMP_FILE.getName()))); } } @@ -169,7 +169,7 @@ private void readDynamicFile(InputStream is) { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(mConfig.TEMP_FILE, true); + fos = new FileOutputStream(getConfig().TEMP_FILE, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); @@ -180,8 +180,8 @@ private void readDynamicFile(InputStream is) { if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= mConfig.END_LOCATION) { - len = (int) (mConfig.END_LOCATION - mChildCurrentLocation); + if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { + len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); bf.flip(); fos.write(bf.array(), 0, len); bf.compact(); @@ -197,7 +197,7 @@ private void readDynamicFile(InputStream is) { handleComplete(); } catch (IOException e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", mConfig.URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); } finally { try { if (fos != null) { @@ -221,8 +221,8 @@ private void readDynamicFile(InputStream is) { private void readNormal(InputStream is) { BufferedRandomAccessFile file = null; try { - file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); - file.seek(mConfig.START_LOCATION); + file = new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); + file.seek(getConfig().START_LOCATION); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; while (isLive() && (len = is.read(buffer)) != -1) { @@ -232,8 +232,8 @@ private void readNormal(InputStream is) { if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= mConfig.END_LOCATION) { - len = (int) (mConfig.END_LOCATION - mChildCurrentLocation); + if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { + len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); file.write(buffer, 0, len); progress(len); break; @@ -244,7 +244,7 @@ private void readNormal(InputStream is) { } } catch (IOException e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", mConfig.URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); } finally { try { if (file != null) { @@ -261,6 +261,6 @@ private void readNormal(InputStream is) { } @Override protected DownloadConfig getTaskConfig() { - return mTaskWrapper.getConfig(); + return getTaskWrapper().getConfig(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java index 4e8c14d9..5ae5eeb8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java @@ -25,6 +25,7 @@ import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.common.http.HttpTaskDelegate; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.BaseException; import com.arialyy.aria.exception.TaskException; @@ -109,22 +110,11 @@ private void handleConnect(HttpURLConnection conn) throws IOException { } } - long len = conn.getContentLength(); - if (len < 0) { - String temp = conn.getHeaderField("Content-Length"); - len = TextUtils.isEmpty(temp) ? -1 : Long.parseLong(temp); - // 某些服务,如果设置了conn.setRequestProperty("Range", "bytes=" + 0 + "-"); - // 会返回 Content-Range: bytes 0-225427911/225427913 - if (len < 0) { - temp = conn.getHeaderField("Content-Range"); - if (TextUtils.isEmpty(temp)) { - len = -1; - } else { - int start = temp.indexOf("/"); - len = Long.parseLong(temp.substring(start + 1)); - } - } + IHttpFileLenAdapter lenAdapter = mTaskWrapper.asHttp().getFileLenAdapter(); + if (lenAdapter == null) { + lenAdapter = new FileLenAdapter(); } + long len = lenAdapter.handleFileLen(conn.getHeaderFields()); if (!CommonUtil.checkSDMemorySpace(mEntity.getDownloadPath(), len)) { failDownload(new TaskException(TAG, @@ -288,7 +278,7 @@ private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOExc if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null") || !newUrl.startsWith( "http")) { if (onFileInfoCallback != null) { - onFileInfoCallback.onFail(mEntity.getUrl(), new TaskException(TAG, "获取重定向链接失败"), false); + onFileInfoCallback.onFail(mEntity, new TaskException(TAG, "获取重定向链接失败"), false); } return; } @@ -327,7 +317,32 @@ private boolean checkLen(long len) { private void failDownload(BaseException e, boolean needRetry) { if (onFileInfoCallback != null) { - onFileInfoCallback.onFail(mEntity.getUrl(), e, needRetry); + onFileInfoCallback.onFail(mEntity, e, needRetry); + } + } + + private static class FileLenAdapter implements IHttpFileLenAdapter { + + @Override public long handleFileLen(Map> headers) { + List sLength = headers.get("Content-Length"); + if (sLength == null || sLength.isEmpty()) { + return -1; + } + String temp = sLength.get(0); + long len = TextUtils.isEmpty(temp) ? -1 : Long.parseLong(temp); + // 某些服务,如果设置了conn.setRequestProperty("Range", "bytes=" + 0 + "-"); + // 会返回 Content-Range: bytes 0-225427911/225427913 + if (len < 0) { + List sRange = headers.get("Content-Range"); + if (sRange == null || sRange.isEmpty()) { + len = -1; + } else { + int start = temp.indexOf("/"); + len = Long.parseLong(temp.substring(start + 1)); + } + } + + return len; } } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java index 2b482674..088a183f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java @@ -55,8 +55,8 @@ final class HttpThreadTask extends AbsThreadTask { HttpThreadTask(StateConstance constance, IDownloadListener listener, SubThreadConfig downloadInfo) { super(constance, listener, downloadInfo); - isOpenDynamicFile = STATE.TASK_RECORD.isOpenDynamicFile; - isBlock = STATE.TASK_RECORD.isBlock; + isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile; + isBlock = getState().TASK_RECORD.isBlock; } @Override public HttpThreadTask call() throws Exception { @@ -69,17 +69,17 @@ final class HttpThreadTask extends AbsThreadTask { BufferedInputStream is = null; BufferedRandomAccessFile file = null; //当前子线程的下载位置 - mChildCurrentLocation = mConfig.START_LOCATION; + mChildCurrentLocation = getConfig().START_LOCATION; try { - HttpTaskDelegate taskDelegate = mTaskWrapper.asHttp(); - URL url = ConnectionHelp.handleUrl(mConfig.URL, taskDelegate); + HttpTaskDelegate taskDelegate = getTaskWrapper().asHttp(); + URL url = ConnectionHelp.handleUrl(getConfig().URL, taskDelegate); conn = ConnectionHelp.handleConnection(url, taskDelegate); - if (mConfig.SUPPORT_BP) { + if (getConfig().SUPPORT_BP) { ALog.d(TAG, - String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID, mConfig.START_LOCATION, mConfig.END_LOCATION)); - conn.setRequestProperty("Range", - String.format("bytes=%s-%s", mConfig.START_LOCATION, (mConfig.END_LOCATION - 1))); + String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); + conn.setRequestProperty("Range", String.format("bytes=%s-%s", getConfig().START_LOCATION, + (getConfig().END_LOCATION - 1))); } else { ALog.w(TAG, "该下载不支持断点"); } @@ -117,24 +117,25 @@ final class HttpThreadTask extends AbsThreadTask { } else { //创建可设置位置的文件 file = - new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); + new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", + getTaskConfig().getBuffSize()); //设置每条线程写入文件的位置 - file.seek(mConfig.START_LOCATION); + file.seek(getConfig().START_LOCATION); readNormal(is, file); handleComplete(); } } catch (MalformedURLException e) { fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", mConfig.TEMP_FILE.getName(), - mEntity.getDownloadPath(), mEntity.getUrl()), e)); + String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + getEntity().getDownloadPath(), getEntity().getUrl()), e)); } catch (IOException e) { fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", mConfig.TEMP_FILE.getName(), - mEntity.getDownloadPath(), mEntity.getUrl()), e)); + String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + getEntity().getDownloadPath(), getEntity().getUrl()), e)); } catch (Exception e) { fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", mConfig.TEMP_FILE.getName(), - mEntity.getDownloadPath(), mEntity.getUrl()), e)); + String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + getEntity().getDownloadPath(), getEntity().getUrl()), e)); } finally { try { if (file != null) { @@ -159,7 +160,7 @@ final class HttpThreadTask extends AbsThreadTask { private void readChunked(InputStream is) { FileOutputStream fos = null; try { - fos = new FileOutputStream(mConfig.TEMP_FILE, true); + fos = new FileOutputStream(getConfig().TEMP_FILE, true); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; while (isLive() && (len = is.read(buffer)) != -1) { @@ -175,7 +176,7 @@ private void readChunked(InputStream is) { handleComplete(); } catch (IOException e) { fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("文件下载失败,savePath: %s, url: %s", mEntity.getDownloadPath(), mConfig.URL), + String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), getConfig().URL), e)); } finally { if (fos != null) { @@ -197,7 +198,7 @@ private void readDynamicFile(InputStream is) { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(mConfig.TEMP_FILE, true); + fos = new FileOutputStream(getConfig().TEMP_FILE, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); @@ -210,8 +211,8 @@ private void readDynamicFile(InputStream is) { if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= mConfig.END_LOCATION) { - len = (int) (mConfig.END_LOCATION - mChildCurrentLocation); + if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { + len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); bf.flip(); fos.write(bf.array(), 0, len); bf.compact(); @@ -227,7 +228,7 @@ private void readDynamicFile(InputStream is) { handleComplete(); } catch (IOException e) { fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("文件下载失败,savePath: %s, url: %s", mEntity.getDownloadPath(), mConfig.URL), + String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), getConfig().URL), e)); } finally { try { @@ -276,48 +277,49 @@ private void handleComplete() { if (!checkBlock()) { return; } - if (mTaskWrapper.asHttp().isChunked()) { + if (getTaskWrapper().asHttp().isChunked()) { ALog.i(TAG, "任务下载完成"); - STATE.isRunning = false; + getState().isRunning = false; mListener.onComplete(); return; } - if (mChildCurrentLocation == mConfig.END_LOCATION) { + if (mChildCurrentLocation == getConfig().END_LOCATION) { //支持断点的处理 - if (mConfig.SUPPORT_BP) { + if (getConfig().SUPPORT_BP) { ALog.i(TAG, - String.format("任务【%s】线程__%s__下载完毕", mConfig.TEMP_FILE.getName(), mConfig.THREAD_ID)); - writeConfig(true, mConfig.END_LOCATION); - STATE.COMPLETE_THREAD_NUM++; - if (STATE.isComplete()) { + String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID)); + writeConfig(true, getConfig().END_LOCATION); + getState().COMPLETE_THREAD_NUM++; + if (getState().isComplete()) { if (isBlock) { boolean success = mergeFile(); if (!success) { - STATE.isRunning = false; + getState().isRunning = false; mListener.onFail(false, new TaskException(TAG, - String.format("任务【%s】分块文件合并失败", mConfig.TEMP_FILE.getName()))); + String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName()))); return; } } - STATE.TASK_RECORD.deleteData(); - STATE.isRunning = false; + getState().TASK_RECORD.deleteData(); + getState().isRunning = false; mListener.onComplete(); } - if (STATE.isFail()) { - STATE.isRunning = false; + if (getState().isFail()) { + getState().isRunning = false; mListener.onFail(false, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", mConfig.TEMP_FILE.getName(), - mEntity.getDownloadPath(), mEntity.getUrl()))); + String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + getEntity().getDownloadPath(), getEntity().getUrl()))); } } else { ALog.i(TAG, "任务下载完成"); - STATE.isRunning = false; + getState().isRunning = false; mListener.onComplete(); } } else { - STATE.FAIL_NUM++; + getState().FAIL_NUM++; } } @@ -326,6 +328,6 @@ private void handleComplete() { } @Override protected DownloadConfig getTaskConfig() { - return mTaskWrapper.getConfig(); + return getTaskWrapper().getConfig(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java index fd77ab50..e04f9f3b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java @@ -20,6 +20,7 @@ import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.common.OnFileInfoCallback; import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IEntity; @@ -131,7 +132,7 @@ private Runnable createInfoThread() { mDownloader.start(); } - @Override public void onFail(String url, BaseException e, boolean needRetry) { + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { failDownload(e, needRetry); mDownloader.closeTimer(); } @@ -143,7 +144,7 @@ private Runnable createInfoThread() { mDownloader.start(); } - @Override public void onFail(String url, BaseException e, boolean needRetry) { + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { failDownload(e, needRetry); mDownloader.closeTimer(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java index a5671f99..be585516 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java @@ -18,6 +18,7 @@ import android.os.Handler; import android.os.Looper; import com.arialyy.aria.core.common.IUtil; +import com.arialyy.aria.core.common.http.HttpTaskDelegate; import com.arialyy.aria.core.config.Configuration; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper; @@ -293,12 +294,31 @@ SubDownloadLoader createSubLoader(DTaskWrapper taskWrapper) { /** * 创建并启动子任务下载器 + * * @param needGetFileInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 */ SubDownloadLoader createSubLoader(DTaskWrapper taskWrapper, boolean needGetFileInfo) { + if (getTaskType() == HTTP_GROUP) { + cloneHeader(taskWrapper); + } SubDownloadLoader loader = new SubDownloadLoader(mScheduler, taskWrapper, needGetFileInfo); mExeLoader.put(loader.getKey(), loader); mSubQueue.startTask(loader); return loader; } + + /** + * 子任务使用父包裹器的属性 + */ + private void cloneHeader(DTaskWrapper taskWrapper) { + HttpTaskDelegate groupDelegate = mGTWrapper.asHttp(); + HttpTaskDelegate subDelegate = taskWrapper.asHttp(); + + // 设置属性 + subDelegate.setFileLenAdapter(groupDelegate.getFileLenAdapter()); + subDelegate.setRequestEnum(groupDelegate.getRequestEnum()); + subDelegate.setHeaders(groupDelegate.getHeaders()); + subDelegate.setProxy(groupDelegate.getProxy()); + subDelegate.setParams(groupDelegate.getParams()); + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java index d9440d2e..4c68d4c9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java @@ -20,8 +20,11 @@ import com.arialyy.aria.core.common.OnFileInfoCallback; import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.downloader.HttpFileInfoThread; +import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.BaseException; import com.arialyy.aria.util.ALog; import java.util.concurrent.ExecutorService; @@ -84,6 +87,7 @@ public DownloadGroupUtil(IDownloadGroupListener listener, DGTaskWrapper taskWrap private void getGroupSize() { new Thread(new Runnable() { int count; + int failCount; @Override public void run() { for (DTaskWrapper dTaskWrapper : mGTWrapper.getSubTaskWrapper()) { @@ -91,13 +95,16 @@ private void getGroupSize() { @Override public void onComplete(String url, CompleteInfo info) { createSubLoader((DTaskWrapper) info.wrapper, false); count++; - checkGetSizeComplete(count); + checkGetSizeComplete(count, failCount); } - @Override public void onFail(String url, BaseException e, boolean needRetry) { - ALog.e(TAG, String.format("获取文件信息失败,url:%s", url)); + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { + ALog.e(TAG, String.format("获取文件信息失败,url:%s", ((DownloadEntity) entity).getUrl())); count++; - checkGetSizeComplete(count); + failCount++; + mListener.onSubFail((DownloadEntity) entity, new AriaIOException(TAG, + String.format("子任务获取文件长度失败,url:%s", ((DownloadEntity) entity).getUrl()))); + checkGetSizeComplete(count, failCount); } })); } @@ -108,17 +115,20 @@ private void getGroupSize() { /** * 检查组合任务大小是否获取完成,获取完成后取消阻塞,并设置组合任务大小 */ - private void checkGetSizeComplete(int count) { + private void checkGetSizeComplete(int count, int failCount) { if (count == mGTWrapper.getSubTaskWrapper().size()) { long size = 0; for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { size += wrapper.getEntity().getFileSize(); } mGTWrapper.getEntity().setFileSize(size); + mGTWrapper.getEntity().update(); + } else if (failCount == mGTWrapper.getSubTaskWrapper().size()) { + mListener.onFail(true, new AriaIOException(TAG, "获取子任务长度失败")); + } - synchronized (LOCK) { - LOCK.notify(); - } + synchronized (LOCK) { + LOCK.notify(); } } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java index ecb73e44..bb800a35 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java @@ -19,6 +19,7 @@ import com.arialyy.aria.core.common.OnFileInfoCallback; import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.exception.BaseException; @@ -49,7 +50,7 @@ public FtpDirDownloadUtil(IDownloadGroupListener listener, DGTaskWrapper taskEnt } } - @Override public void onFail(String url, BaseException e, boolean needRetry) { + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { mListener.onFail(needRetry, e); } }).start(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDownloadLoader.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDownloadLoader.java index ca825f69..abdbe115 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDownloadLoader.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDownloadLoader.java @@ -24,6 +24,7 @@ import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.downloader.Downloader; import com.arialyy.aria.core.download.downloader.HttpFileInfoThread; +import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.scheduler.ISchedulers; import com.arialyy.aria.exception.BaseException; @@ -104,7 +105,7 @@ void reStart() { mDownloader.start(); } - @Override public void onFail(String url, BaseException e, boolean needRetry) { + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { mSchedulers.obtainMessage(ISchedulers.FAIL, SubDownloadLoader.this); } })).start(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsHttpFileLenAdapter.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsHttpFileLenAdapter.java new file mode 100644 index 00000000..035cd5a3 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsHttpFileLenAdapter.java @@ -0,0 +1,10 @@ +package com.arialyy.aria.core.inf; + +public abstract class AbsHttpFileLenAdapter implements IHttpFileLenAdapter { + + @Override public Object clone() throws CloneNotSupportedException { + //AbsHttpFileLenAdapter newAdapter = super.clone(); + + return super.clone(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java index 4493164e..f36cdeda 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java @@ -1,348 +1,340 @@ -/* - * 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.core.inf; - -import android.support.annotation.CheckResult; -import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.command.ICmd; -import com.arialyy.aria.core.command.normal.CancelCmd; -import com.arialyy.aria.core.command.normal.NormalCmdFactory; -import com.arialyy.aria.core.common.TaskRecord; -import com.arialyy.aria.core.download.DGTaskWrapper; -import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.manager.TaskWrapperManager; -import com.arialyy.aria.core.upload.UTaskWrapper; -import com.arialyy.aria.orm.DbEntity; -import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by AriaL on 2017/7/3. - */ -public abstract class AbsTarget implements ITargetHandler { - - protected String TAG; - private AbsEntity mEntity; - private AbsTaskWrapper mTaskWrapper; - private String mTargetName; - - protected AbsTarget() { - TAG = CommonUtil.getClassName(this); - } - - public void setTaskWrapper(AbsTaskWrapper wrapper) { - mTaskWrapper = wrapper; - mEntity = wrapper.getEntity(); - } - - /** - * 重置状态,将任务状态设置为未开始状态 - * 注意:如果在后续方法调用链中没有调用 {@link #start()}、{@link #stop()}、{@link #cancel()}、{@link #resume()} - * 等操作任务的方法,那么你需要调用{@link #save()}才能将修改保存到数据库 - */ - @CheckResult(suggest = "after use #start()、#stop()、#cancel()、#resume()、#save()?") - public TARGET resetState() { - mTaskWrapper.getEntity().setState(IEntity.STATE_WAIT); - mTaskWrapper.setRefreshInfo(true); - return (TARGET) this; - } - - /** - * 删除记录,如果任务正在执行,则会删除正在下载的任务 - */ - public void removeRecord() { - if (isRunning()) { - ALog.d("AbsTarget", "任务正在下载,即将删除任务"); - cancel(); - } else { - if (mEntity instanceof AbsNormalEntity) { - TaskRecord record = - DbEntity.findFirst(TaskRecord.class, "TaskRecord.filePath=?", mTaskWrapper.getKey()); - if (record != null) { - CommonUtil.delTaskRecord(record, mTaskWrapper.isRemoveFile(), (AbsNormalEntity) mEntity); - } else { - mEntity.deleteData(); - } - } else if (mEntity instanceof DownloadGroupEntity) { - CommonUtil.delGroupTaskRecord(mTaskWrapper.isRemoveFile(), ((DownloadGroupEntity) mEntity)); - } - TaskWrapperManager.getInstance().removeTaskWrapper(mEntity.getKey()); - } - } - - public AbsEntity getEntity() { - return mEntity; - } - - public String getTargetName() { - return mTargetName; - } - - public void setTargetName(String mTargetName) { - this.mTargetName = mTargetName; - } - - /** - * 获取任务实体 - */ - public AbsTaskWrapper getTaskWrapper() { - return mTaskWrapper; - } - - /** - * 获取任务进度,如果任务存在,则返回当前进度 - * - * @return 该任务进度 - */ - public long getCurrentProgress() { - return mEntity == null ? -1 : mEntity.getCurrentProgress(); - } - - /** - * 获取任务文件大小 - * - * @return 文件大小 - */ - public long getFileSize() { - return mEntity == null ? 0 : mEntity.getFileSize(); - } - - /** - * 获取单位转换后的文件大小 - * - * @return 文件大小{@code xxx mb} - */ - public String getConvertFileSize() { - return mEntity == null ? "0b" : CommonUtil.formatFileSize(mEntity.getFileSize()); - } - - /** - * 设置扩展字段,用来保存你的其它数据,如果你的数据比较多,你可以把你的数据转换为JSON字符串,然后再存到Aria中 - * 注意:如果在后续方法调用链中没有调用 {@link #start()}、{@link #stop()}、{@link #cancel()}、{@link #resume()} - * 等操作任务的方法,那么你需要调用{@link #save()}才能将修改保存到数据库 - * - * @param str 扩展数据 - */ - @CheckResult(suggest = "after use #start()、#stop()、#cancel()、#resume()、#save()?") - public TARGET setExtendField(String str) { - if (TextUtils.isEmpty(str)) return (TARGET) this; - if (TextUtils.isEmpty(mEntity.getStr()) || !mEntity.getStr().equals(str)) { - mEntity.setStr(str); - } else { - ALog.e(TAG, "设置扩展字段失败,扩展字段为一致"); - } - - return (TARGET) this; - } - - /** - * 获取存放的扩展字段 - * 设置扩展字段{@link #setExtendField(String)} - */ - public String getExtendField() { - return mEntity.getStr(); - } - - /** - * 获取任务状态 - * - * @return {@link IEntity} - */ - public int getTaskState() { - return mEntity.getState(); - } - - /** - * 获取任务进度百分比 - * - * @return 返回任务进度 - */ - public int getPercent() { - if (mEntity == null) { - ALog.e("AbsTarget", "下载管理器中没有该任务"); - return 0; - } - if (mEntity.getFileSize() != 0) { - return (int) (mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()); - } - return 0; - } - - /** - * 检查实体是否合法,如果实体合法,将保存实体到数据库,或更新数据库中的实体对象 - * - * @return {@code true} 合法 - */ - protected abstract boolean checkEntity(); - - protected int checkTaskType() { - int taskType = 0; - if (mTaskWrapper instanceof DTaskWrapper) { - taskType = ICmd.TASK_TYPE_DOWNLOAD; - } else if (mTaskWrapper instanceof DGTaskWrapper) { - taskType = ICmd.TASK_TYPE_DOWNLOAD_GROUP; - } else if (mTaskWrapper instanceof UTaskWrapper) { - taskType = ICmd.TASK_TYPE_UPLOAD; - } - return taskType; - } - - /** - * 保存修改 - */ - @Override public void save() { - if (!checkEntity()) { - ALog.e(TAG, "保存修改失败"); - } else { - ALog.i(TAG, "保存成功"); - } - } - - /** - * 任务是否在执行 - * - * @return {@code true} 任务正在执行 - */ - public abstract boolean isRunning(); - - /** - * 任务是否存在 - * - * @return {@code true} 任务存在 - */ - public abstract boolean taskExists(); - - /** - * 设置target类型 - * - * @return {@link #HTTP}、{@link #FTP}、{@link #GROUP_HTTP}、{@link #GROUP_FTP_DIR} - */ - public abstract int getTargetType(); - - /** - * 添加任务 - */ - @Override public void add() { - if (checkEntity()) { - AriaManager.getInstance(AriaManager.APP) - .setCmd(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CREATE, - checkTaskType())) - .exe(); - } - } - - /** - * 开始任务 - */ - @Override public void start() { - if (checkEntity()) { - AriaManager.getInstance(AriaManager.APP) - .setCmd( - CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_START, - checkTaskType())) - .exe(); - } - } - - /** - * 停止任务 - * - * @see #stop() - */ - @Deprecated public void pause() { - if (checkEntity()) { - stop(); - } - } - - @Override public void stop() { - if (checkEntity()) { - AriaManager.getInstance(AriaManager.APP) - .setCmd( - CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_STOP, checkTaskType())) - .exe(); - } - } - - /** - * 恢复任务 - */ - @Override public void resume() { - if (checkEntity()) { - AriaManager.getInstance(AriaManager.APP) - .setCmd( - CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_START, - checkTaskType())) - .exe(); - } - } - - /** - * 删除任务 - */ - @Override public void cancel() { - if (checkEntity()) { - AriaManager.getInstance(AriaManager.APP) - .setCmd(CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_CANCEL, - checkTaskType())) - .exe(); - } - } - - /** - * 任务重试 - */ - @Override public void reTry() { - if (checkEntity()) { - List cmds = new ArrayList<>(); - int taskType = checkTaskType(); - cmds.add( - CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_STOP, taskType)); - cmds.add(CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_START, taskType)); - AriaManager.getInstance(AriaManager.APP).setCmds(cmds).exe(); - } - } - - /** - * 删除任务 - * - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 - * {@code false}如果任务已经完成,只删除任务数据库记录, - */ - @Override public void cancel(boolean removeFile) { - if (checkEntity()) { - CancelCmd cancelCmd = - (CancelCmd) CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_CANCEL, - checkTaskType()); - cancelCmd.removeFile = removeFile; - AriaManager.getInstance(AriaManager.APP).setCmd(cancelCmd).exe(); - } - } - - /** - * 重新下载 - */ - @Override public void reStart() { - if (checkEntity()) { - cancel(); - start(); - } - } -} +/* + * 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.core.inf; + +import android.support.annotation.CheckResult; +import android.text.TextUtils; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.command.ICmd; +import com.arialyy.aria.core.command.normal.CancelCmd; +import com.arialyy.aria.core.command.normal.NormalCmdFactory; +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.manager.TaskWrapperManager; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by AriaL on 2017/7/3. + */ +public abstract class AbsTarget implements ITargetHandler { + + protected String TAG; + private AbsEntity mEntity; + private AbsTaskWrapper mTaskWrapper; + private String mTargetName; + + protected AbsTarget() { + TAG = CommonUtil.getClassName(this); + } + + public void setTaskWrapper(AbsTaskWrapper wrapper) { + mTaskWrapper = wrapper; + mEntity = wrapper.getEntity(); + } + + /** + * 重置状态,将任务状态设置为未开始状态 + * 注意:如果在后续方法调用链中没有调用 {@link #start()}、{@link #stop()}、{@link #cancel()}、{@link #resume()} + * 等操作任务的方法,那么你需要调用{@link #save()}才能将修改保存到数据库 + */ + @CheckResult(suggest = "after use #start()、#stop()、#cancel()、#resume()、#save()?") + public TARGET resetState() { + mTaskWrapper.getEntity().setState(IEntity.STATE_WAIT); + mTaskWrapper.setRefreshInfo(true); + return (TARGET) this; + } + + /** + * 删除记录,如果任务正在执行,则会删除正在下载的任务 + */ + public void removeRecord() { + if (isRunning()) { + ALog.d("AbsTarget", "任务正在下载,即将删除任务"); + cancel(); + } else { + if (mEntity instanceof AbsNormalEntity) { + CommonUtil.delTaskRecord((AbsNormalEntity) mEntity, mTaskWrapper.isRemoveFile()); + } else if (mEntity instanceof DownloadGroupEntity) { + CommonUtil.delGroupTaskRecord(((DownloadGroupEntity) mEntity), mTaskWrapper.isRemoveFile()); + } + TaskWrapperManager.getInstance().removeTaskWrapper(mEntity.getKey()); + } + } + + public AbsEntity getEntity() { + return mEntity; + } + + public String getTargetName() { + return mTargetName; + } + + public void setTargetName(String mTargetName) { + this.mTargetName = mTargetName; + } + + /** + * 获取任务实体 + */ + public AbsTaskWrapper getTaskWrapper() { + return mTaskWrapper; + } + + /** + * 获取任务进度,如果任务存在,则返回当前进度 + * + * @return 该任务进度 + */ + public long getCurrentProgress() { + return mEntity == null ? -1 : mEntity.getCurrentProgress(); + } + + /** + * 获取任务文件大小 + * + * @return 文件大小 + */ + public long getFileSize() { + return mEntity == null ? 0 : mEntity.getFileSize(); + } + + /** + * 获取单位转换后的文件大小 + * + * @return 文件大小{@code xxx mb} + */ + public String getConvertFileSize() { + return mEntity == null ? "0b" : CommonUtil.formatFileSize(mEntity.getFileSize()); + } + + /** + * 设置扩展字段,用来保存你的其它数据,如果你的数据比较多,你可以把你的数据转换为JSON字符串,然后再存到Aria中 + * 注意:如果在后续方法调用链中没有调用 {@link #start()}、{@link #stop()}、{@link #cancel()}、{@link #resume()} + * 等操作任务的方法,那么你需要调用{@link #save()}才能将修改保存到数据库 + * + * @param str 扩展数据 + */ + @CheckResult(suggest = "after use #start()、#stop()、#cancel()、#resume()、#save()?") + public TARGET setExtendField(String str) { + if (TextUtils.isEmpty(str)) return (TARGET) this; + if (TextUtils.isEmpty(mEntity.getStr()) || !mEntity.getStr().equals(str)) { + mEntity.setStr(str); + } else { + ALog.e(TAG, "设置扩展字段失败,扩展字段为一致"); + } + + return (TARGET) this; + } + + /** + * 获取存放的扩展字段 + * 设置扩展字段{@link #setExtendField(String)} + */ + public String getExtendField() { + return mEntity.getStr(); + } + + /** + * 获取任务状态 + * + * @return {@link IEntity} + */ + public int getTaskState() { + return mEntity.getState(); + } + + /** + * 获取任务进度百分比 + * + * @return 返回任务进度 + */ + public int getPercent() { + if (mEntity == null) { + ALog.e("AbsTarget", "下载管理器中没有该任务"); + return 0; + } + if (mEntity.getFileSize() != 0) { + return (int) (mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()); + } + return 0; + } + + /** + * 检查实体是否合法,如果实体合法,将保存实体到数据库,或更新数据库中的实体对象 + * + * @return {@code true} 合法 + */ + protected abstract boolean checkEntity(); + + protected int checkTaskType() { + int taskType = 0; + if (mTaskWrapper instanceof DTaskWrapper) { + taskType = ICmd.TASK_TYPE_DOWNLOAD; + } else if (mTaskWrapper instanceof DGTaskWrapper) { + taskType = ICmd.TASK_TYPE_DOWNLOAD_GROUP; + } else if (mTaskWrapper instanceof UTaskWrapper) { + taskType = ICmd.TASK_TYPE_UPLOAD; + } + return taskType; + } + + /** + * 保存修改 + */ + @Override public void save() { + if (!checkEntity()) { + ALog.e(TAG, "保存修改失败"); + } else { + ALog.i(TAG, "保存成功"); + } + } + + /** + * 任务是否在执行 + * + * @return {@code true} 任务正在执行 + */ + public abstract boolean isRunning(); + + /** + * 任务是否存在 + * + * @return {@code true} 任务存在 + */ + public abstract boolean taskExists(); + + /** + * 设置target类型 + * + * @return {@link #HTTP}、{@link #FTP}、{@link #GROUP_HTTP}、{@link #GROUP_FTP_DIR} + */ + public abstract int getTargetType(); + + /** + * 添加任务 + */ + @Override public void add() { + if (checkEntity()) { + AriaManager.getInstance(AriaManager.APP) + .setCmd(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CREATE, + checkTaskType())) + .exe(); + } + } + + /** + * 开始任务 + */ + @Override public void start() { + if (checkEntity()) { + AriaManager.getInstance(AriaManager.APP) + .setCmd( + CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_START, + checkTaskType())) + .exe(); + } + } + + /** + * 停止任务 + * + * @see #stop() + */ + @Deprecated public void pause() { + if (checkEntity()) { + stop(); + } + } + + @Override public void stop() { + if (checkEntity()) { + AriaManager.getInstance(AriaManager.APP) + .setCmd( + CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_STOP, checkTaskType())) + .exe(); + } + } + + /** + * 恢复任务 + */ + @Override public void resume() { + if (checkEntity()) { + AriaManager.getInstance(AriaManager.APP) + .setCmd( + CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_START, + checkTaskType())) + .exe(); + } + } + + /** + * 删除任务 + */ + @Override public void cancel() { + if (checkEntity()) { + AriaManager.getInstance(AriaManager.APP) + .setCmd(CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_CANCEL, + checkTaskType())) + .exe(); + } + } + + /** + * 任务重试 + */ + @Override public void reTry() { + if (checkEntity()) { + List cmds = new ArrayList<>(); + int taskType = checkTaskType(); + cmds.add( + CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_STOP, taskType)); + cmds.add(CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_START, taskType)); + AriaManager.getInstance(AriaManager.APP).setCmds(cmds).exe(); + } + } + + /** + * 删除任务 + * + * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 + * {@code false}如果任务已经完成,只删除任务数据库记录, + */ + @Override public void cancel(boolean removeFile) { + if (checkEntity()) { + CancelCmd cancelCmd = + (CancelCmd) CommonUtil.createNormalCmd(mTaskWrapper, NormalCmdFactory.TASK_CANCEL, + checkTaskType()); + cancelCmd.removeFile = removeFile; + AriaManager.getInstance(AriaManager.APP).setCmd(cancelCmd).exe(); + } + } + + /** + * 重新下载 + */ + @Override public void reStart() { + if (checkEntity()) { + cancel(); + start(); + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpFileLenAdapter.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpFileLenAdapter.java new file mode 100644 index 00000000..55b8c507 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpFileLenAdapter.java @@ -0,0 +1,36 @@ +/* + * 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.core.inf; + +import java.io.Serializable; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; + +/** + * Http文件长度适配器 + */ +public interface IHttpFileLenAdapter extends Serializable { + long serialVersionUID = 1L; + + /** + * 同伙header中的数据获取文件长度 + * + * @param headers header参数{@link URLConnection#getHeaderFields()} + * @return 文件长度 + */ + long handleFileLen(Map> headers); +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java index ed4e8ca2..5e6ae55d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java @@ -1,194 +1,194 @@ -/* - * 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.core.upload.uploader; - -import aria.apache.commons.net.ftp.FTPClient; -import aria.apache.commons.net.ftp.FTPReply; -import aria.apache.commons.net.ftp.OnFtpInputStreamListener; -import com.arialyy.aria.core.common.StateConstance; -import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; -import com.arialyy.aria.core.config.BaseTaskConfig; -import com.arialyy.aria.core.config.DownloadConfig; -import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.inf.IEventListener; -import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.core.upload.UTaskWrapper; -import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.exception.TaskException; -import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.BufferedRandomAccessFile; -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * Created by Aria.Lao on 2017/7/28. FTP 单线程上传任务,需要FTP 服务器给用户打开append和write的权限 - */ -class FtpThreadTask extends AbsFtpThreadTask { - private final String TAG = "FtpThreadTask"; - private String dir, remotePath; - - FtpThreadTask(StateConstance constance, IEventListener listener, - SubThreadConfig info) { - super(constance, listener, info); - } - - @Override public int getMaxSpeed() { - return getTaskConfig().getMaxSpeed(); - } - - @Override protected UploadConfig getTaskConfig() { - return mTaskWrapper.getConfig(); - } - - @Override public FtpThreadTask call() throws Exception { - super.call(); - //当前子线程的下载位置 - mChildCurrentLocation = mConfig.START_LOCATION; - FTPClient client = null; - BufferedRandomAccessFile file = null; - try { - ALog.d(TAG, - String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", mConfig.TEMP_FILE.getName(), - mConfig.THREAD_ID, mConfig.START_LOCATION, mConfig.END_LOCATION)); - client = createClient(); - if (client == null) { - return this; - } - initPath(); - client.makeDirectory(dir); - client.changeWorkingDirectory(dir); - client.setRestartOffset(mConfig.START_LOCATION); - int reply = client.getReplyCode(); - if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.FILE_ACTION_OK) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, - String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, - client.getReplyString(), mEntity.getFilePath()))); - client.disconnect(); - return this; - } - - file = new BufferedRandomAccessFile(mConfig.TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); - if (mConfig.START_LOCATION != 0) { - //file.skipBytes((int) mConfig.START_LOCATION); - file.seek(mConfig.START_LOCATION); - } - boolean complete = upload(client, file); - if (!complete || isBreak()) { - return this; - } - ALog.i(TAG, - String.format("任务【%s】线程__%s__上传完毕", mConfig.TEMP_FILE.getName(), mConfig.THREAD_ID)); - writeConfig(true, mConfig.END_LOCATION); - STATE.COMPLETE_THREAD_NUM++; - if (STATE.isComplete()) { - STATE.TASK_RECORD.deleteData(); - STATE.isRunning = false; - mListener.onComplete(); - } - if (STATE.isFail()) { - STATE.isRunning = false; - mListener.onFail(false, new TaskException(TAG, - String.format("上传失败,filePath: %s, uploadUrl: %s", mEntity.getFilePath(), mConfig.URL))); - } - } catch (IOException e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("上传失败,filePath: %s, uploadUrl: %s", mEntity.getFilePath(), mConfig.URL))); - } catch (Exception e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, null, e)); - } finally { - try { - if (file != null) { - file.close(); - } - if (client != null && client.isConnected()) { - client.disconnect(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return this; - } - - private void initPath() throws UnsupportedEncodingException { - dir = new String(mTaskWrapper.asFtp().getUrlEntity().remotePath.getBytes(charSet), - SERVER_CHARSET); - remotePath = new String(String.format("%s/%s", mTaskWrapper.asFtp().getUrlEntity().remotePath, - mEntity.getFileName()).getBytes(charSet), SERVER_CHARSET); - } - - /** - * 上传 - * - * @return {@code true}上传成功、{@code false} 上传失败 - */ - private boolean upload(final FTPClient client, final BufferedRandomAccessFile bis) - throws IOException { - - try { - ALog.d(TAG, String.format("remotePath: %s", remotePath)); - client.storeFile(remotePath, new FtpFISAdapter(bis), new OnFtpInputStreamListener() { - boolean isStoped = false; - - @Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred, - int bytesTransferred, long streamSize) { - try { - if (isBreak() && !isStoped) { - isStoped = true; - client.abor(); - } - if (mSpeedBandUtil != null) { - mSpeedBandUtil.limitNextBytes(bytesTransferred); - } - progress(bytesTransferred); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - } catch (IOException e) { - String msg = String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", client.getReplyCode(), - client.getReplyString(), mEntity.getFilePath()); - if (client.isConnected()) { - client.disconnect(); - } - if (e.getMessage().contains("AriaIOException caught while copying")) { - e.printStackTrace(); - } else { - fail(mChildCurrentLocation, - new AriaIOException(TAG, msg, e)); - } - return false; - } - - int reply = client.getReplyCode(); - if (!FTPReply.isPositiveCompletion(reply)) { - if (reply != FTPReply.TRANSFER_ABORTED) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, - String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, - client.getReplyString(), mEntity.getFilePath()))); - } - if (client.isConnected()) { - client.disconnect(); - } - return false; - } - return true; - } -} +/* + * 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.core.upload.uploader; + +import aria.apache.commons.net.ftp.FTPClient; +import aria.apache.commons.net.ftp.FTPReply; +import aria.apache.commons.net.ftp.OnFtpInputStreamListener; +import com.arialyy.aria.core.common.StateConstance; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; +import com.arialyy.aria.core.config.BaseTaskConfig; +import com.arialyy.aria.core.config.DownloadConfig; +import com.arialyy.aria.core.config.UploadConfig; +import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.exception.AriaIOException; +import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * Created by Aria.Lao on 2017/7/28. FTP 单线程上传任务,需要FTP 服务器给用户打开append和write的权限 + */ +class FtpThreadTask extends AbsFtpThreadTask { + private final String TAG = "FtpThreadTask"; + private String dir, remotePath; + + FtpThreadTask(StateConstance constance, IEventListener listener, + SubThreadConfig info) { + super(constance, listener, info); + } + + @Override public int getMaxSpeed() { + return getTaskConfig().getMaxSpeed(); + } + + @Override protected UploadConfig getTaskConfig() { + return getTaskWrapper().getConfig(); + } + + @Override public FtpThreadTask call() throws Exception { + super.call(); + //当前子线程的下载位置 + mChildCurrentLocation = getConfig().START_LOCATION; + FTPClient client = null; + BufferedRandomAccessFile file = null; + try { + ALog.d(TAG, + String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), + getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); + client = createClient(); + if (client == null) { + return this; + } + initPath(); + client.makeDirectory(dir); + client.changeWorkingDirectory(dir); + client.setRestartOffset(getConfig().START_LOCATION); + int reply = client.getReplyCode(); + if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.FILE_ACTION_OK) { + fail(mChildCurrentLocation, + new AriaIOException(TAG, + String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, + client.getReplyString(), getEntity().getFilePath()))); + client.disconnect(); + return this; + } + + file = new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); + if (getConfig().START_LOCATION != 0) { + //file.skipBytes((int) getConfig().START_LOCATION); + file.seek(getConfig().START_LOCATION); + } + boolean complete = upload(client, file); + if (!complete || isBreak()) { + return this; + } + ALog.i(TAG, + String.format("任务【%s】线程__%s__上传完毕", getConfig().TEMP_FILE.getName(), getConfig().THREAD_ID)); + writeConfig(true, getConfig().END_LOCATION); + getState().COMPLETE_THREAD_NUM++; + if (getState().isComplete()) { + getState().TASK_RECORD.deleteData(); + getState().isRunning = false; + mListener.onComplete(); + } + if (getState().isFail()) { + getState().isRunning = false; + mListener.onFail(false, new TaskException(TAG, + String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(), getConfig().URL))); + } + } catch (IOException e) { + fail(mChildCurrentLocation, new AriaIOException(TAG, + String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(), getConfig().URL))); + } catch (Exception e) { + fail(mChildCurrentLocation, new AriaIOException(TAG, null, e)); + } finally { + try { + if (file != null) { + file.close(); + } + if (client != null && client.isConnected()) { + client.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return this; + } + + private void initPath() throws UnsupportedEncodingException { + dir = new String(getTaskWrapper().asFtp().getUrlEntity().remotePath.getBytes(charSet), + SERVER_CHARSET); + remotePath = new String(String.format("%s/%s", getTaskWrapper().asFtp().getUrlEntity().remotePath, + getEntity().getFileName()).getBytes(charSet), SERVER_CHARSET); + } + + /** + * 上传 + * + * @return {@code true}上传成功、{@code false} 上传失败 + */ + private boolean upload(final FTPClient client, final BufferedRandomAccessFile bis) + throws IOException { + + try { + ALog.d(TAG, String.format("remotePath: %s", remotePath)); + client.storeFile(remotePath, new FtpFISAdapter(bis), new OnFtpInputStreamListener() { + boolean isStoped = false; + + @Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred, + int bytesTransferred, long streamSize) { + try { + if (isBreak() && !isStoped) { + isStoped = true; + client.abor(); + } + if (mSpeedBandUtil != null) { + mSpeedBandUtil.limitNextBytes(bytesTransferred); + } + progress(bytesTransferred); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } catch (IOException e) { + String msg = String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", client.getReplyCode(), + client.getReplyString(), getEntity().getFilePath()); + if (client.isConnected()) { + client.disconnect(); + } + if (e.getMessage().contains("AriaIOException caught while copying")) { + e.printStackTrace(); + } else { + fail(mChildCurrentLocation, + new AriaIOException(TAG, msg, e)); + } + return false; + } + + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + if (reply != FTPReply.TRANSFER_ABORTED) { + fail(mChildCurrentLocation, + new AriaIOException(TAG, + String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, + client.getReplyString(), getEntity().getFilePath()))); + } + if (client.isConnected()) { + client.disconnect(); + } + return false; + } + return true; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java index 19846126..cfb9b99f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java @@ -59,18 +59,18 @@ class HttpThreadTask extends AbsThreadTask { @Override public HttpThreadTask call() throws Exception { super.call(); - File uploadFile = new File(mEntity.getFilePath()); + File uploadFile = new File(getEntity().getFilePath()); if (!uploadFile.exists()) { fail(new TaskException(TAG, - String.format("上传失败,文件不存在;filePath: %s, url: %s", mEntity.getFilePath(), - mEntity.getUrl()))); + String.format("上传失败,文件不存在;filePath: %s, url: %s", getEntity().getFilePath(), + getEntity().getUrl()))); return this; } mListener.onPre(); URL url; try { - url = new URL(mEntity.getUrl()); - HttpTaskDelegate taskDelegate = mTaskWrapper.asHttp(); + url = new URL(getEntity().getUrl()); + HttpTaskDelegate taskDelegate = getTaskWrapper().asHttp(); mHttpConn = (HttpURLConnection) url.openConnection(); mHttpConn.setRequestMethod(taskDelegate.getRequestEnum().name); mHttpConn.setUseCaches(false); @@ -100,12 +100,13 @@ class HttpThreadTask extends AbsThreadTask { addFormField(writer, key, taskDelegate.getFormFields().get(key)); } uploadFile(writer, taskDelegate.getAttachment(), uploadFile); - mTaskWrapper.getEntity().setResponseStr(finish(writer)); + getEntity().setResponseStr(finish(writer)); mListener.onComplete(); } catch (Exception e) { e.printStackTrace(); fail(new TaskException(TAG, - String.format("上传失败,filePath: %s, url: %s", mEntity.getFilePath(), mEntity.getUrl()), e)); + String.format("上传失败,filePath: %s, url: %s", getEntity().getFilePath(), + getEntity().getUrl()), e)); } return this; } @@ -113,7 +114,7 @@ class HttpThreadTask extends AbsThreadTask { private void fail(BaseException e1) { try { mListener.onFail(true, e1); - STATE.isRunning = false; + getState().isRunning = false; if (mOutputStream != null) { mOutputStream.close(); } @@ -132,7 +133,7 @@ private void addFormField(PrintWriter writer, String name, String value) { .append("\"") .append(LINE_END); writer.append("Content-Type: text/plain; charset=") - .append(mTaskWrapper.asHttp().getCharSet()) + .append(getTaskWrapper().asHttp().getCharSet()) .append(LINE_END); writer.append(LINE_END); writer.append(value).append(LINE_END); @@ -151,11 +152,11 @@ private void uploadFile(PrintWriter writer, String attachment, File uploadFile) writer.append("Content-Disposition: form-data; name=\"") .append(attachment) .append("\"; filename=\"") - .append(mTaskWrapper.getEntity().getFileName()) + .append(getEntity().getFileName()) .append("\"") .append(LINE_END); writer.append("Content-Type: ") - .append(URLConnection.guessContentTypeFromName(mTaskWrapper.getEntity().getFileName())) + .append(URLConnection.guessContentTypeFromName(getEntity().getFileName())) .append(LINE_END); writer.append("Content-Transfer-Encoding: binary").append(LINE_END); writer.append(LINE_END); @@ -165,9 +166,9 @@ private void uploadFile(PrintWriter writer, String attachment, File uploadFile) byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { - STATE.CURRENT_LOCATION += bytesRead; + getState().CURRENT_LOCATION += bytesRead; mOutputStream.write(buffer, 0, bytesRead); - if (STATE.isCancel) { + if (getState().isCancel) { break; } if (mSpeedBandUtil != null) { @@ -180,11 +181,11 @@ private void uploadFile(PrintWriter writer, String attachment, File uploadFile) inputStream.close(); writer.append(LINE_END); writer.flush(); - if (STATE.isCancel) { - STATE.isRunning = false; + if (getState().isCancel) { + getState().isRunning = false; return; } - STATE.isRunning = false; + getState().isRunning = false; } /** @@ -224,6 +225,6 @@ private String finish(PrintWriter writer) throws IOException { } @Override protected UploadConfig getTaskConfig() { - return mTaskWrapper.getConfig(); + return getTaskWrapper().getConfig(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java index fe185f48..e7fc8162 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java @@ -18,6 +18,7 @@ import com.arialyy.aria.core.common.CompleteInfo; import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.common.OnFileInfoCallback; +import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.inf.IUploadListener; import com.arialyy.aria.core.upload.UTaskWrapper; @@ -61,7 +62,7 @@ public SimpleUploadUtil(UTaskWrapper taskWrapper, IUploadListener listener) { } } - @Override public void onFail(String url, BaseException e, boolean needRetry) { + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { mListener.onFail(needRetry, e); } }).start(); diff --git a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java index d19c0908..c2f5cd33 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java @@ -1,1233 +1,1219 @@ -/* - * 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 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 getPkgClassNames(Context context, String className) { - List 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 getPkgClassName(String path, String filterClass) { - List 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 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, tempUrl.length()); - } - } else { - int tempEnd = url.lastIndexOf("/"); - if (tempEnd > 0) { - fileName = url.substring(tempEnd + 1, url.length()); - } - } - 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 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 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(boolean removeFile, DownloadGroupEntity groupEntity) { - if (groupEntity == null) { - ALog.e(TAG, "删除下载任务组记录失败,任务组实体为null"); - return; - } - List 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 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(TaskRecord record, boolean removeFile, AbsNormalEntity dEntity) { - if (dEntity == null) return; - File file; - if (dEntity instanceof DownloadEntity) { - file = new File(((DownloadEntity) dEntity).getDownloadPath()); - } else if (dEntity instanceof UploadEntity) { - file = new File(((UploadEntity) dEntity).getFilePath()); - } else { - ALog.w(TAG, "删除记录失败,未知类型"); - return; - } - if (removeFile || !dEntity.isComplete()) { - // 删除分块文件 - 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(); - } - } - } - if (file.exists()) { - file.delete(); - } - } - - if (record != null) { - record.deleteData(); - } - //下载任务实体和下载实体为一对一关系,下载实体删除,任务实体自动删除 - dEntity.deleteData(); - } - - /** - * 删除任务记录,默认删除文件 - * - * @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); - 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 AbsNormalCmd createNormalCmd(T entity, int cmd, - int taskType) { - return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType); - } - - /** - * 创建任务组命令 - * - * @param childUrl 子任务url - */ - public static 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 getAllFields(Class clazz) { - List 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(); - } - } - } +/* + * 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 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 getPkgClassNames(Context context, String className) { + List 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 getPkgClassName(String path, String filterClass) { + List 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 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 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 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 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 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 AbsNormalCmd createNormalCmd(T entity, int cmd, + int taskType) { + return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType); + } + + /** + * 创建任务组命令 + * + * @param childUrl 子任务url + */ + public static 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 getAllFields(Class clazz) { + List 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(); + } + } + } } \ No newline at end of file diff --git a/DEV_LOG.md b/DEV_LOG.md index 15653872..ed590c27 100644 --- a/DEV_LOG.md +++ b/DEV_LOG.md @@ -11,6 +11,8 @@ - 修复使用`Content-Disposition`的文件名时,第一次下载无法重命名文件的问题 - 修复使用`Content-Disposition`的文件名时,多次重命名文件的问题 - 组合任务新增`unknownSize()`,用于处理组合任务大小未知的情况,https://github.com/AriaLyy/Aria/issues/380 + - 优化`AbsThreadTask`代码 + - 新增文件长度处理功能 https://github.com/AriaLyy/Aria/issues/393 + v_3.6.3 (2019/4/2) - fix bug https://github.com/AriaLyy/Aria/issues/377 + v_3.6.2 (2019/4/1) diff --git a/app/build.gradle b/app/build.gradle index cad8130d..a2a0e16f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,6 +55,9 @@ dependencies { // annotationProcessor 'com.arialyy.aria:aria-compiler:3.3.14' api 'com.github.PhilJay:MPAndroidChart:v3.0.3' implementation project(':AppFrame') + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' + implementation 'com.github.bumptech.glide:glide:3.7.0' + } repositories { mavenCentral() diff --git a/app/src/main/java/com/arialyy/simple/base/BaseApplication.java b/app/src/main/java/com/arialyy/simple/base/BaseApplication.java index 3295868e..6b094c01 100644 --- a/app/src/main/java/com/arialyy/simple/base/BaseApplication.java +++ b/app/src/main/java/com/arialyy/simple/base/BaseApplication.java @@ -24,6 +24,7 @@ import com.arialyy.frame.core.AbsFrame; import com.arialyy.simple.BuildConfig; import com.arialyy.simple.common.ConnectionChangeReceiver; +import com.squareup.leakcanary.LeakCanary; /** * Created by Lyy on 2016/9/27. @@ -44,12 +45,20 @@ public class BaseApplication extends Application { .build()); StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); - } - - registerReceiver(new ConnectionChangeReceiver(), - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + if (LeakCanary.isInAnalyzerProcess(this)) {//1 + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return; + } + LeakCanary.install(this); } + registerReceiver(new ConnectionChangeReceiver(), + new + + IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); +} + public static BaseApplication getApp() { return INSTANCE; } diff --git a/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java index ed4a407a..2a93f2e7 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.Environment; +import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -36,6 +37,7 @@ import com.arialyy.aria.core.download.DownloadTarget; import com.arialyy.aria.core.download.DownloadTask; import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.core.scheduler.ISchedulers; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; @@ -58,8 +60,8 @@ public class SingleTaskActivity extends BaseActivity { //"http://static.gaoshouyou.com/d/22/94/822260b849944492caadd2983f9bb624.apks"; //"http://120.55.95.61:8811/ghcg/zg/武义总规纲要成果.zip"; //"https://yizi-kejian.oss-cn-beijing.aliyuncs.com/qimeng/package1/qmtable11.zip"; - //"http://rs.0.gaoshouyou.com/d/04/1e/400423a7551e1f3f0eb1812afa1f9b44.apk"; - "http://chargepile2.techsum.net/car-manage/file/download?path=2019-04-26/c0242efd18be4ecbb23911b1c509dcad--掌通各系统汇总.xls"; // 无长度的chunked + "http://rs.0.gaoshouyou.com/d/04/1e/400423a7551e1f3f0eb1812afa1f9b44.apk"; + //"http://chargepile2.techsum.net/car-manage/file/download?path=2019-04-26/c0242efd18be4ecbb23911b1c509dcad--掌通各系统汇总.xls"; // 无长度的chunked //"http://58.210.9.131/tpk/sipgt//TDLYZTGH.tpk"; //chunked 下载 //"http://apk500.bce.baidu-mgame.com/game/67000/67734/20170622040827_oem_5502845.apk?r=1"; //"https://dl.genymotion.com/releases/genymotion-2.12.1/genymotion-2.12.1-vbox.exe"; @@ -262,8 +264,8 @@ void taskComplete(DownloadTask task) { L.d(TAG, "path = " + task.getDownloadEntity().getDownloadPath()); L.d(TAG, "md5Code = " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); L.d(TAG, "data = " + Aria.download(this).getDownloadEntity(DOWNLOAD_URL)); - DownloadEntity temp = Aria.download(this).getDownloadEntity(DOWNLOAD_URL); - L.d(TAG, "status = " + temp.getState() + ", isComplete = " + temp.isComplete()); + //DownloadEntity temp = Aria.download(this).getDownloadEntity(DOWNLOAD_URL); + //L.d(TAG, "status = " + temp.getState() + ", isComplete = " + temp.isComplete()); //Intent install = new Intent(Intent.ACTION_VIEW); //install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //File apkFile = new File(task.getDownloadPath()); @@ -289,15 +291,9 @@ public void onClick(View view) { switch (view.getId()) { case R.id.start: startD(); - //List list = Aria.download(this).getAllNotCompleteTask(5, 2); - //ALog.d("Tag", list.size() + ""); break; case R.id.stop: Aria.download(this).load(DOWNLOAD_URL).stop(); - //startActivity(new Intent(this, SingleTaskActivity.class)); - //Aria.download(this).unRegister(); - //Aria.download(this).load(DOWNLOAD_URL).removeRecord(); - //Log.d(TAG, Aria.download(this).taskExists(DOWNLOAD_URL) + ""); break; case R.id.cancel: Aria.download(this).load(DOWNLOAD_URL).cancel(true); @@ -310,9 +306,6 @@ public void onClick(View view) { private void startD() { String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ggsg14.apk"; - //Map params = new HashMap<>(); - //params.put("key", "value"); - //params.put("filename", "CentOS-7-x86_64-Minimal-1804.iso"); Aria.download(SingleTaskActivity.this) .load(DOWNLOAD_URL) //.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") @@ -321,6 +314,18 @@ private void startD() { //.addHeader("Cookie", "BAIDUID=648E5FF020CC69E8DD6F492D1068AAA9:FG=1; BIDUPSID=648E5FF020CC69E8DD6F492D1068AAA9; PSTM=1519099573; BD_UPN=12314753; locale=zh; BDSVRTM=0") .useServerFileName(true) .setFilePath(path, true) + .setFileLenAdapter(new IHttpFileLenAdapter() { + @Override public long handleFileLen(Map> headers) { + + List sLength = headers.get("Content-Length"); + if (sLength == null || sLength.isEmpty()) { + return -1; + } + String temp = sLength.get(0); + + return Long.parseLong(temp); + } + }) //.asGet() //.asPost() //.setParams(params) diff --git a/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java b/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java index 8ff0b7da..3e7bbd1a 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java @@ -24,6 +24,8 @@ import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupTask; +import com.arialyy.aria.core.inf.AbsHttpFileLenAdapter; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.frame.util.show.L; import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; @@ -31,6 +33,7 @@ import com.arialyy.simple.databinding.ActivityDownloadGroupBinding; import com.arialyy.simple.widget.SubStateLinearLayout; import java.util.List; +import java.util.Map; /** * Created by lyy on 2017/7/6. @@ -87,6 +90,18 @@ public void onClick(View view) { //.setSubFileName(getModule(GroupModule.class).getSubName2()) .setSubFileName(getModule(GroupModule.class).getSubName()) .unknownSize() + .setFileLenAdapter(new AbsHttpFileLenAdapter() { + @Override public long handleFileLen(Map> headers) { + + List sLength = headers.get("Content-Length"); + if (sLength == null || sLength.isEmpty()) { + return -1; + } + String temp = sLength.get(0); + + return Long.parseLong(temp); + } + }) //.setFileSize(114981416) //.updateUrls(temp) .start();