modify BlockManager.kt

v4
laoyuyu 2 years ago
parent d5cc67391e
commit 61ba26e58a
  1. 33
      Http/src/main/java/com/arialyy/aria/http/download/HttpDEventListener.kt
  2. 6
      Http/src/main/java/com/arialyy/aria/http/download/HttpDStartController.kt
  3. 18
      Http/src/main/java/com/arialyy/aria/http/download/HttpDTaskUtil.kt
  4. 249
      Http/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java
  5. 9
      Http/src/main/java/com/arialyy/aria/http/download/TimerInterceptor.kt
  6. 4
      M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8Listener.java
  7. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/inf/ITaskOption.java
  8. 48
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/AbsEventListener.java
  9. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseDListener.java
  10. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseUListener.java
  11. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/DownloadGroupListener.java
  12. 3
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/IEventListener.java
  13. 265
      PublicComponent/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java
  14. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTask.java
  15. 5
      PublicComponent/src/main/java/com/arialyy/aria/core/task/DBlockManager.kt
  16. 1
      PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadTask.java
  17. 4
      PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask2.kt
  18. 82
      PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTaskManager2.kt
  19. 6
      PublicComponent/src/main/java/com/arialyy/aria/orm/dao/DEntityDao.kt
  20. 388
      PublicComponent/src/main/java/com/arialyy/aria/util/FileUri.kt
  21. 18
      PublicComponent/src/main/java/com/arialyy/aria/util/FileUtils.kt
  22. 12
      Queue/src/main/java/com/arialyy/aria/queue/AbsTaskQueue.java

@ -0,0 +1,33 @@
/*
* 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.http.download
import com.arialyy.aria.core.listener.AbsEventListener
import com.arialyy.aria.core.task.DownloadTask
import com.arialyy.aria.util.FileUtils
class HttpDEventListener(task: DownloadTask) : AbsEventListener(task) {
override fun handleCancel() {
}
override fun handleComplete() {
}
}

@ -123,8 +123,8 @@ class HttpDStartController(target: Any, val url: String) : HttpBaseController(ta
*/
private suspend fun findDEntityBySavePath(option: HttpDTaskOption): DEntity {
val savePath = option.savePathUri
val dao = DuaContext.getServiceManager().getDbService().getDuaDb()?.getDEntityDao()
val de = dao?.getDEntityBySavePath(savePath.toString())
val dao = DuaContext.getServiceManager().getDbService().getDuaDb().getDEntityDao()
val de = dao.getDEntityBySavePath(savePath.toString())
if (de != null) {
return de
}
@ -132,7 +132,7 @@ class HttpDStartController(target: Any, val url: String) : HttpBaseController(ta
sourceUrl = option.sourUrl!!,
savePath = savePath!!,
)
dao?.insert(newDe)
dao.insert(newDe)
return newDe
}

@ -20,7 +20,9 @@ import com.arialyy.aria.core.DuaContext
import com.arialyy.aria.core.inf.IBlockManager
import com.arialyy.aria.core.task.AbsTaskUtil
import com.arialyy.aria.core.task.BlockManager
import com.arialyy.aria.core.task.DownloadTask
import com.arialyy.aria.core.task.TaskResp
import com.arialyy.aria.core.task.ThreadTaskManager2
import com.arialyy.aria.exception.AriaException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -33,6 +35,12 @@ import kotlinx.coroutines.launch
internal class HttpDTaskUtil : AbsTaskUtil() {
private var blockManager: BlockManager? = null
init {
getTask().getTaskOption(HttpDTaskOption::class.java).eventListener =
HttpDEventListener(getTask() as DownloadTask)
}
override fun getBlockManager(): IBlockManager {
if (blockManager == null) {
blockManager = BlockManager(getTask())
@ -45,11 +53,15 @@ internal class HttpDTaskUtil : AbsTaskUtil() {
}
override fun cancel() {
TODO("Not yet implemented")
DuaContext.duaScope.launch(Dispatchers.IO) {
ThreadTaskManager2.stopThreadTask(getTask().taskId, true)
}
}
override fun stop() {
blockManager?.stop()
DuaContext.duaScope.launch(Dispatchers.IO) {
ThreadTaskManager2.stopThreadTask(getTask().taskId)
}
}
override fun start() {
@ -67,7 +79,7 @@ internal class HttpDTaskUtil : AbsTaskUtil() {
addCoreInterceptor(HttpBlockThreadInterceptor())
val resp = interceptor()
if (resp == null || resp.code != TaskResp.CODE_SUCCESS) {
getTask().getTaskOption(HttpDTaskOption::class.java).taskListener.onFail(
getTask().getTaskOption(HttpDTaskOption::class.java).eventListener.onFail(
false,
AriaException("start task fail, task interrupt, code: ${resp?.code ?: TaskResp.CODE_INTERRUPT}")
)

@ -1,249 +0,0 @@
/*
* 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.http.download;
import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.task.AbsThreadTaskAdapter;
import com.arialyy.aria.core.task.ThreadConfig;
import com.arialyy.aria.exception.AriaHTTPException;
import com.arialyy.aria.http.ConnectionHelp;
import com.arialyy.aria.http.HttpOption;
import com.arialyy.aria.http.request.IRequest;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Map;
import java.util.Set;
import timber.log.Timber;
/**
* Created by lyy on 2017/1/18. 下载线程
*/
final class HttpDThreadTaskAdapter extends AbsThreadTaskAdapter {
HttpDThreadTaskAdapter(ThreadConfig threadConfig) {
super(threadConfig);
}
private HttpDTaskOption getTaskOption() {
return (HttpDTaskOption) getThreadConfig().getOption();
}
@Override protected void handlerThreadTask() {
HttpURLConnection conn = null;
BufferedInputStream is = null;
BufferedRandomAccessFile file = null;
try {
HttpDTaskOption taskOption = getTaskOption();
HttpOption option = taskOption.getHttpOption();
conn = IRequest.Companion.getRequest(option).getDConnection(taskOption.getSourUrl(), option);
if (!taskOption.isSupportResume()) {
Timber.w("this task not support resume, url: %s", taskOption.getSourUrl());
}else {
conn.setRequestProperty("Range",
String.format("bytes=%s-%s", getThreadConfig().getBlockRecord().getStartLocation(),
(getThreadConfig().getBlockRecord().getEndLocation() - 1)));
}
ConnectionHelp.setConnectParam(mTaskOption, conn);
conn.setConnectTimeout(getTaskConfig().getConnectTimeOut());
conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数
if (mTaskOption.isChunked()) {
conn.setDoInput(true);
conn.setChunkedStreamingMode(0);
}
conn.connect();
is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn));
if (mTaskOption.isChunked()) {
readChunked(is);
} else if (getThreadConfig().isBlock) {
readDynamicFile(is);
} else {
//创建可设置位置的文件
file =
new BufferedRandomAccessFile(getThreadConfig().tempFile, "rwd",
getTaskConfig().getBuffSize());
//设置每条线程写入文件的位置
if (getThreadRecord().startLocation > 0) {
file.seek(getThreadRecord().startLocation);
}
readNormal(is, file);
handleComplete();
}
} catch (MalformedURLException e) {
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getFilePath(), getEntity().getUrl()), e), false);
} catch (IOException e) {
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getFilePath(), getEntity().getUrl()), e), true);
} catch (ArrayIndexOutOfBoundsException e) {
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getFilePath(), getEntity().getUrl()), e), false);
} catch (Exception e) {
fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getFilePath(), getEntity().getUrl()), e), false);
} finally {
try {
if (file != null) {
file.close();
}
if (is != null) {
is.close();
}
if (conn != null) {
conn.getInputStream().close();
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取chunked数据
*/
private void readChunked(InputStream is) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(getThreadConfig().tempFile, true);
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len;
while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) {
if (getThreadTask().isBreak()) {
break;
}
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
fos.write(buffer, 0, len);
progress(len);
}
handleComplete();
} catch (IOException e) {
fail(new AriaHTTPException(
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(),
getThreadConfig().url), e), true);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 动态长度文件读取方式
*/
private void readDynamicFile(InputStream is) {
FileOutputStream fos = null;
FileChannel foc = null;
ReadableByteChannel fic = null;
try {
int len;
fos = new FileOutputStream(getThreadConfig().tempFile, true);
foc = fos.getChannel();
fic = Channels.newChannel(is);
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
//如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。
while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) {
if (getThreadTask().isBreak()) {
break;
}
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
if (getRangeProgress() + len >= getThreadRecord().endLocation) {
len = (int) (getThreadRecord().endLocation - getRangeProgress());
bf.flip();
fos.write(bf.array(), 0, len);
bf.compact();
progress(len);
break;
} else {
bf.flip();
foc.write(bf);
bf.compact();
progress(len);
}
}
handleComplete();
} catch (IOException e) {
fail(new AriaHTTPException(
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(),
getThreadConfig().url), e), true);
} finally {
try {
if (fos != null) {
fos.flush();
fos.close();
}
if (foc != null) {
foc.close();
}
if (fic != null) {
fic.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取普通的文件流
*/
private void readNormal(InputStream is, BufferedRandomAccessFile file)
throws IOException {
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len;
while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) {
if (getThreadTask().isBreak()) {
break;
}
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
file.write(buffer, 0, len);
progress(len);
}
}
@Override public void cancel() {
}
@Override public void stop() {
}
}

@ -15,9 +15,8 @@
*/
package com.arialyy.aria.http.download
import com.arialyy.aria.core.inf.IBlockManager
import com.arialyy.aria.core.inf.ITaskOption
import com.arialyy.aria.core.manager.ThreadTaskManager
import com.arialyy.aria.core.task.ThreadTaskManager2
import com.arialyy.aria.core.task.ITask
import com.arialyy.aria.core.task.ITaskInterceptor
import com.arialyy.aria.core.task.TaskChain
@ -61,12 +60,12 @@ open class TimerInterceptor : ITaskInterceptor {
|| blockManager.hasFailedBlock()
|| !isRunning(chain.getTask())
) {
ThreadTaskManager.getInstance().removeTaskThread(chain.getTask().taskId)
ThreadTaskManager2.stopThreadTask(chain.getTask().taskId)
closeTimer()
return
}
if (chain.getTask().taskState.curProgress >= 0) {
chain.getTask().getTaskOption(ITaskOption::class.java).taskListener.onProgress(
chain.getTask().getTaskOption(ITaskOption::class.java).eventListener.onProgress(
blockManager.currentProgress
)
return
@ -86,6 +85,6 @@ open class TimerInterceptor : ITaskInterceptor {
}
@Synchronized fun isRunning(task: ITask): Boolean {
return ThreadTaskManager.getInstance().taskIsRunning(task.taskId)
return ThreadTaskManager2.taskIsRunning(task.taskId)
}
}

@ -19,7 +19,7 @@ import android.os.Bundle;
import android.os.Message;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.listener.BaseListener;
import com.arialyy.aria.core.listener.AbsEventListener;
import com.arialyy.aria.core.listener.IDLoadListener;
import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.task.DownloadTask;
@ -29,7 +29,7 @@ import com.arialyy.aria.util.DeleteM3u8Record;
/**
* 下载监听类
*/
public final class M3U8Listener extends BaseListener implements IDLoadListener {
public final class M3U8Listener extends AbsEventListener implements IDLoadListener {
@Override
public void onPostPre(long fileSize) {

@ -23,6 +23,6 @@ import com.arialyy.aria.core.listener.IEventListener;
*/
public abstract class ITaskOption {
public IEventListener taskListener;
public IEventListener eventListener;
public int threadNum;
}

@ -20,37 +20,41 @@ import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.DuaContext;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.task.TaskCachePool;
import com.arialyy.aria.core.task.TaskState;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.exception.AriaException;
import com.arialyy.aria.core.task.TaskCachePool;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.ErrorHelp;
import java.lang.ref.WeakReference;
import timber.log.Timber;
public abstract class BaseListener implements IEventListener {
public abstract class AbsEventListener implements IEventListener {
static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度
protected Handler outHandler;
private long mLastLen; //上一次发送长度
private boolean isFirst = true;
private ITask mTask;
private final ITask mTask;
long mLastSaveTime;
private long mUpdateInterval;
private final long mUpdateInterval;
/**
* 处理任务取消
*/
protected abstract void handleCancel();
protected abstract void handleComplete();
@Override public IEventListener setParams(ITask task) {
protected AbsEventListener(ITask task) {
this.outHandler = DuaContext.INSTANCE.getServiceManager().getSchedulerHandler();
mTask = new WeakReference<>(task).get();
mUpdateInterval = AriaConfig.getInstance().getCConfig().getUpdateInterval();
mLastLen = task.getTaskState().getCurProgress();
mLastSaveTime = System.currentTimeMillis();
return this;
}
protected <TASK extends AbsTask> TASK getTask(Class<TASK> clazz) {
return (TASK) mTask;
protected ITask getTask() {
return mTask;
}
@Override public void onPre() {
@ -93,7 +97,7 @@ public abstract class BaseListener implements IEventListener {
}
@Override public void onComplete() {
saveData(IEntity.STATE_COMPLETE, mEntity.getFileSize());
saveData(IEntity.STATE_COMPLETE, mTask.getTaskState().getFileSize());
handleSpeed(0);
sendInState2Target(ISchedulers.COMPLETE);
}
@ -128,30 +132,8 @@ public abstract class BaseListener implements IEventListener {
speed = speed * 1000 / mUpdateInterval;
}
mTask.getTaskState().setSpeed(speed);
int taskType = mTaskWrapper.getRequestType();
if (taskType != ITaskWrapper.M3U8_VOD && taskType != ITaskWrapper.M3U8_LIVE) {
mEntity.setPercent((int) (mEntity.getFileSize() <= 0 ? 0
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
}
}
/**
* 处理任务完成后的情况
*/
private void handleComplete() {
mEntity.setComplete(true);
mEntity.setCompleteTime(System.currentTimeMillis());
mEntity.setCurrentProgress(mEntity.getFileSize());
mEntity.setPercent(100);
handleSpeed(0);
}
/**
* 处理任务取消
*/
protected abstract void handleCancel();
/**
* 将任务状态发送给下载器
*

@ -24,7 +24,7 @@ import com.arialyy.aria.util.DeleteDRecord;
/**
* 下载监听类
*/
public class BaseDListener extends BaseListener implements IDLoadListener {
public class BaseDListener extends AbsEventListener implements IDLoadListener {
@Override
public void onPostPre(long fileSize) {

@ -23,7 +23,7 @@ import com.arialyy.aria.util.DeleteURecord;
/**
* 下载监听类
*/
public class BaseUListener extends BaseListener implements IUploadListener {
public class BaseUListener extends AbsEventListener implements IUploadListener {
@Override protected void handleCancel() {
int sType = getTask(UploadTask.class).getSchedulerType();

@ -34,7 +34,7 @@ import static com.arialyy.aria.core.task.AbsTask.ERROR_INFO_KEY;
/**
* Created by Aria.Lao on 2017/7/20. 任务组下载事件
*/
public class DownloadGroupListener extends BaseListener implements IDGroupListener {
public class DownloadGroupListener extends AbsEventListener implements IDGroupListener {
private GroupSendParams<DownloadGroupTask, DownloadEntity> mSeedEntity;
@Override public IEventListener setParams(AbsTask task, Handler outHandler) {

@ -15,7 +15,6 @@
*/
package com.arialyy.aria.core.listener;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.exception.AriaException;
/**
@ -24,8 +23,6 @@ import com.arialyy.aria.exception.AriaException;
*/
public interface IEventListener {
IEventListener setParams(ITask task);
/**
* 预处理有时有些地址链接比较慢这时可以先在这个地方出来一些界面上的UI如按钮的状态
*/

@ -1,265 +0,0 @@
/*
* 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.manager;
import android.text.TextUtils;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程任务管理器
*/
@Deprecated
public class ThreadTaskManager1 {
private final String TAG = CommonUtil.getClassName(this);
private static volatile ThreadTaskManager INSTANCE = null;
private static final int CORE_POOL_NUM = 20;
private static final ReentrantLock LOCK = new ReentrantLock();
private final ThreadPoolExecutor mExePool;
private final Map<Integer, Set<FutureContainer>> mThreadTasks = new ConcurrentHashMap<>();
public static synchronized ThreadTaskManager getInstance() {
if (INSTANCE == null) {
INSTANCE = new ThreadTaskManager();
}
return INSTANCE;
}
private ThreadTaskManager() {
mExePool = new ThreadPoolExecutor(CORE_POOL_NUM, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
mExePool.allowsCoreThreadTimeOut();
}
/**
* 删除所有线程任务
*/
public void removeAllThreadTask() {
if (mThreadTasks.isEmpty()) {
return;
}
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
for (Set<FutureContainer> threads : mThreadTasks.values()) {
for (FutureContainer container : threads) {
if (container.future.isDone() || container.future.isCancelled()) {
continue;
}
container.threadTask.destroy();
}
threads.clear();
}
mThreadTasks.clear();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 启动线程任务
*
* @param taskId {@link ITask#getTaskId()}
* @param threadTask 线程任务{@link IThreadTask}
*/
public void startThread(Integer taskId, IThreadTask threadTask) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return;
}
Set<FutureContainer> temp = mThreadTasks.get(taskId);
if (temp == null) {
temp = new HashSet<>();
mThreadTasks.put(taskId, temp);
}
FutureContainer container = new FutureContainer();
container.threadTask = threadTask;
container.future = mExePool.submit(threadTask);
temp.add(container);
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 任务是否在执行
*
* @return {@code true} 任务正在运行
*/
public boolean taskIsRunning(Integer taskId) {
return mThreadTasks.get(taskId) != null;
}
/**
* 停止任务的所有线程
*/
public void removeTaskThread(Integer taskId) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return;
}
Set<FutureContainer> temp = mThreadTasks.get(taskId);
if (temp != null && temp.size() > 0) {
for (FutureContainer container : temp) {
if (container.future.isDone() || container.future.isCancelled()) {
continue;
}
container.threadTask.destroy();
}
temp.clear();
mThreadTasks.remove(taskId);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 根据线程名删除任务的中的线程
*
* @param threadName 线程名
* @return true 删除线程成功false 删除线程失败
*/
public boolean removeSingleTaskThread(Integer taskId, String threadName) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return false;
}
if (TextUtils.isEmpty(threadName)) {
ALog.e(TAG, "线程名为空");
return false;
}
Set<FutureContainer> temp = mThreadTasks.get(taskId);
if (temp != null && temp.size() > 0) {
FutureContainer tempC = null;
for (FutureContainer container : temp) {
if (container.threadTask.getThreadName().equals(threadName)) {
tempC = container;
break;
}
}
if (tempC != null) {
tempC.threadTask.destroy();
temp.remove(tempC);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
return false;
}
/**
* 删除单个线程任务
*
* @param task 线程任务
*/
public boolean removeSingleTaskThread(Integer taskId, IThreadTask task) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return false;
}
if (task == null) {
ALog.e(TAG, "线程任务为空");
return false;
}
Set<FutureContainer> temp = mThreadTasks.get(taskId);
if (temp != null && temp.size() > 0) {
FutureContainer tempC = null;
for (FutureContainer container : temp) {
if (container.threadTask == task) {
tempC = container;
break;
}
}
if (tempC != null) {
task.destroy();
temp.remove(tempC);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
return false;
}
/**
* 重试线程任务
*
* @param task 线程任务
*/
public void retryThread(IThreadTask task) {
try {
LOCK.tryLock(2, TimeUnit.SECONDS);
if (mExePool.isShutdown()) {
ALog.e(TAG, "线程池已经关闭");
return;
}
try {
if (task == null || task.isDestroy()) {
ALog.e(TAG, "线程为空或线程已经中断");
return;
}
} catch (Exception e) {
ALog.e(TAG, "", e);
return;
}
mExePool.submit(task);
} catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
private static class FutureContainer {
Future future;
IThreadTask threadTask;
}
}

@ -45,7 +45,7 @@ public abstract class AbsTask implements ITask {
mUtil = util;
taskId = TaskStatePool.INSTANCE.buildTaskId$PublicComponent_debug();
TaskStatePool.INSTANCE.putTaskState(getTaskId(), mTaskState);
util.init(this, taskOption.taskListener);
util.init(this, taskOption.eventListener);
}
@Override public void setState(int state) {

@ -30,7 +30,6 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.atomic.AtomicInteger
@ -55,19 +54,21 @@ class BlockManager(task: ITask) : IBlockManager {
private lateinit var handler: Handler
private var blockNum: Int = 1
private var eventListener: IEventListener =
task.getTaskOption(ITaskOption::class.java).taskListener
task.getTaskOption(ITaskOption::class.java).eventListener
private val callback = Callback { msg ->
when (msg.what) {
IBlockManager.STATE_STOP -> {
stoppedNum.getAndIncrement()
if (isStopped) {
eventListener.onStop(currentProgress)
quitLooper()
}
}
IBlockManager.STATE_CANCEL -> {
canceledNum.getAndIncrement()
if (isCanceled) {
eventListener.onCancel()
quitLooper()
}
}

@ -29,7 +29,6 @@ public class DownloadTask extends AbsTask {
public DownloadTask(DTaskOption taskOption, ITaskUtil util) {
super(taskOption, util);
taskOption.taskListener.setParams(this);
}
public Uri getSavePath() {

@ -51,13 +51,13 @@ class ThreadTask2(
}
override fun cancel() {
adapter.cancel()
adapter.breakTask()
isCanceled = true
handler.obtainMessage(IBlockManager.STATE_CANCEL)
}
override fun stop() {
adapter.stop()
adapter.breakTask()
isStopped = true
handler.obtainMessage(IBlockManager.STATE_STOP)
}

@ -0,0 +1,82 @@
/*
* 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.task
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit.SECONDS
import java.util.concurrent.locks.ReentrantLock
object ThreadTaskManager2 {
private val mThreadTasks: ConcurrentHashMap<Int, MutableSet<IThreadTask>> = ConcurrentHashMap()
private val LOCK = ReentrantLock()
/**
* 任务是否在执行
*
* @return `true` 任务正在运行
*/
fun taskIsRunning(taskId: Int): Boolean {
return mThreadTasks[taskId] != null
}
/**
* stop thread task
* @param isRemoveTask if true, remove task and block
*/
fun stopThreadTask(taskId: Int, isRemoveTask: Boolean = false) {
try {
LOCK.tryLock(2, SECONDS)
val threadTaskList: MutableSet<IThreadTask>? = mThreadTasks[taskId]
threadTaskList?.forEach {
if (isRemoveTask) {
it.cancel()
return@forEach
}
it.stop()
}
mThreadTasks.remove(taskId)
} catch (e: Exception) {
e.printStackTrace()
} finally {
LOCK.unlock()
}
}
/**
* 删除所有线程任务
*/
fun removeAllThreadTask() {
if (mThreadTasks.isEmpty()) {
return
}
try {
LOCK.tryLock(2, SECONDS)
for (threads in mThreadTasks.values) {
for (tt in threads) {
tt.stop()
}
threads.clear()
}
mThreadTasks.clear()
} catch (e: InterruptedException) {
e.printStackTrace()
} finally {
LOCK.unlock()
}
}
}

@ -36,13 +36,13 @@ interface DEntityDao {
suspend fun getDEntityList(): List<DEntity>
@Query("SELECT * FROM DEntity WHERE :savePath=savePath")
suspend fun getDEntityBySavePath(savePath: String): DEntity
suspend fun getDEntityBySavePath(savePath: String): DEntity?
@Query("SELECT * FROM DEntity WHERE :dId=dId")
suspend fun getDEntityById(did: String): DEntity
suspend fun getDEntityById(did: String): DEntity?
@Query("SELECT * FROM DEntity WHERE :sourceUrl=sourceUrl")
suspend fun getDEntityBySource(sourceUrl: String): DEntity
suspend fun getDEntityBySource(sourceUrl: String): DEntity?
@Insert
suspend fun insert(dEntity: DEntity)

@ -0,0 +1,388 @@
package com.arialyy.aria.util
import android.annotation.SuppressLint
import android.content.*
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.*
import androidx.core.content.FileProvider
import com.arialyy.aria.core.DuaContext
import timber.log.Timber
import java.io.*
/**
* # FileUri
*
* - Uri & Path Tool
*
* @author javakam
* @date 2020/8/24 11:24
*/
object FileUri {
//Android R
//----------------------------------------------------------------
/**
* `MANAGE_EXTERNAL_STORAGE` 权限检查
*
* @return `true` Have permission
*/
fun isExternalStorageManager(): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Environment.isExternalStorageManager() else false
/**
* 跳转到 `MANAGE_EXTERNAL_STORAGE` 权限设置页面
*
* @return `true` Has been set
*/
fun jumpManageAppAllFilesPermissionSetting(
context: Context,
isNewTask: Boolean = false,
): Boolean {
if (isExternalStorageManager()) return true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:${context.packageName}")
if (isNewTask) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
} catch (e: Exception) {
Timber.e("jumpManageAppAllFilesPermissionSetting: $e")
}
}
return false
}
//从 FilePath 中获取 Uri (Get Uri from FilePath)
//----------------------------------------------------------------
fun getUriByPath(path: String?): Uri? =
if (path.isNullOrBlank()) null else getUriByFile(File(path))
/**
* Return a content URI for a given file.
*
* @param file The file.
* @param isOriginal true content:// or file:// ; false file://xxx
* @return a content URI for a given file
*/
fun getUriByFile(file: File?, isOriginal: Boolean = false): Uri? {
return if (isOriginal) Uri.fromFile(file)
else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val authority = DuaContext.context.packageName + AUTHORITY
FileProvider.getUriForFile(DuaContext.context, authority, file ?: return null)
} else {
Uri.fromFile(file)
}
}
}
fun getShareUri(path: String?): Uri? =
if (path.isNullOrBlank()) null else getUriByFile(File(path), isOriginal = false)
/**
* @return content:// or file://
*/
fun getShareUri(file: File?): Uri? = getUriByFile(file, isOriginal = false)
fun getOriginalUri(path: String?): Uri? =
if (path.isNullOrBlank()) null else getUriByFile(File(path), isOriginal = true)
/**
* @return file://xxx
*/
fun getOriginalUri(file: File?): Uri? = getUriByFile(file, isOriginal = true)
//获取Uri对应的文件路径, Compatible with API 26
//----------------------------------------------------------------
/**
* ### Get the file path through Uri
*
* - Need permission: RequiresPermission(permission.READ_EXTERNAL_STORAGE)
*
* - Modified from: https://github.com/coltoscosmin/FileUtils/blob/master/FileUtils.java
*
* @return file path
*/
fun getPathByUri(uri: Uri?): String? {
return uri?.use {
Timber.i(
"FileUri getPathByUri -> " +
"Uri: " + uri +
", Authority: " + uri.authority +
", Fragment: " + uri.fragment +
", Port: " + uri.port +
", Query: " + uri.query +
", Scheme: " + uri.scheme +
", Host: " + uri.host +
", Segments: " + uri.pathSegments.toString()
)
// 以 file:// 开头的使用第三方应用打开 (open with third-party applications starting with file://)
if (ContentResolver.SCHEME_FILE.equals(uri.scheme, ignoreCase = true)) return getDataColumn(
uri
)
@SuppressLint("ObsoleteSdkInt")
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// Before 4.4 , API 19 content:// 开头, 比如 content://media/external/images/media/123
if (!isKitKat && ContentResolver.SCHEME_CONTENT.equals(uri.scheme, true)) {
if (isGooglePhotosUri(uri)) return uri.lastPathSegment
return getDataColumn(uri)
}
val context = DuaContext.context
// After 4.4 , API 19
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// LocalStorageProvider
if (isLocalStorageDocument(uri)) {
// The path is the id
return DocumentsContract.getDocumentId(uri);
}
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
.toString() + File.separator + split[1]
} else {
@Suppress("DEPRECATION")
return Environment.getExternalStorageDirectory()
.toString() + File.separator + split[1]
}
} else if ("home".equals(type, ignoreCase = true)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
.toString() + File.separator + "documents" + File.separator + split[1]
} else {
@Suppress("DEPRECATION")
return Environment.getExternalStorageDirectory()
.toString() + File.separator + "documents" + File.separator + split[1]
}
} else {
@Suppress("DEPRECATION")
val sdcardPath =
Environment.getExternalStorageDirectory()
.toString() + File.separator + "documents" + File.separator + split[1]
return if (sdcardPath.startsWith("file://")) {
sdcardPath.replace("file://", "")
} else {
sdcardPath
}
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
if (id != null && id.startsWith("raw:")) {
return id.substring(4)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
val contentUriPrefixesToTry = arrayOf(
"content://downloads/public_downloads",
"content://downloads/my_downloads",
"content://downloads/all_downloads"
)
for (contentUriPrefix in contentUriPrefixesToTry) {
val contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), id.toLong())
try {
val path = getDataColumn(contentUri)
if (!path.isNullOrBlank()) return path
} catch (e: Exception) {
Timber.e(e.toString())
}
}
} else {
//testPath(uri)
return getDataColumn(uri)
}
}
// MediaProvider
else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val contentUri: Uri? = when (split[0]) {
"image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
"video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
"audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
"download" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Downloads.EXTERNAL_CONTENT_URI
} else null
else -> null
}
val selectionArgs = arrayOf(split[1])
return getDataColumn(contentUri, "_id=?", selectionArgs)
}
//GoogleDriveProvider
else if (isGoogleDriveUri(uri)) {
return getGoogleDriveFilePath(uri, context)
}
}
// MediaStore (and general)
else if ("content".equals(uri.scheme, ignoreCase = true)) {
// Return the remote address
if (isGooglePhotosUri(uri)) {
return uri.lastPathSegment
}
// Google drive legacy provider
else if (isGoogleDriveUri(uri)) {
return getGoogleDriveFilePath(uri, context)
}
// Huawei
else if (isHuaWeiUri(uri)) {
val uriPath = getDataColumn(uri) ?: uri.toString()
//content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/
if (uriPath.startsWith("/root")) {
return uriPath.replace("/root".toRegex(), "")
}
}
return getDataColumn(uri)
}
return getDataColumn(uri)
}
}
/**
* BUG : 部分机型进入"文件管理器" 执行到 cursor.getColumnIndexOrThrow(column);出现
* Caused by: java.lang.IllegalArgumentException: column '_data' does not exist. Available columns: []
*
* Fixed :
* https://stackoverflow.com/questions/42508383/illegalargumentexception-column-data-does-not-exist
*
*/
private fun getDataColumn(
uri: Uri?,
selection: String? = null,
selectionArgs: Array<String>? = null
): String? {
@Suppress("DEPRECATION")
val column = MediaStore.Files.FileColumns.DATA
val projection = arrayOf(column)
try {
DuaContext.context.contentResolver.query(
uri ?: return null,
projection,
selection,
selectionArgs,
null
)?.use { c: Cursor ->
if (c.moveToFirst()) {
val columnIndex = c.getColumnIndex(column)
return c.getString(columnIndex)
}
}
} catch (e: Throwable) {
Timber.e("getDataColumn -> ${e.message}")
}
return null
}
//The Uri to check
//----------------------------------------------------------------
private fun getGoogleDriveFilePath(uri: Uri, context: Context): String? {
context.contentResolver.query(uri, null, null, null, null)?.use { c: Cursor ->
/*
Get the column indexes of the data in the Cursor,
move to the first row in the Cursor, get the data, and display it.
*/
val nameIndex: Int = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
//val sizeIndex: Int = c.getColumnIndex(OpenableColumns.SIZE)
if (!c.moveToFirst()) {
return uri.toString()
}
val name: String = c.getString(nameIndex)
//val size = c.getLong(sizeIndex).toString()
val file = File(context.cacheDir, name)
var inputStream: InputStream? = null
var outputStream: FileOutputStream? = null
try {
inputStream = context.contentResolver.openInputStream(uri)
outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable: Int = inputStream?.available() ?: 0
val bufferSize = bytesAvailable.coerceAtMost(maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream?.read(buffers)?.also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
inputStream?.close()
outputStream?.close()
}
return file.path
}
return uri.toString()
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri?): Boolean {
return "com.google.android.apps.photos.content".equals(uri?.authority, true)
}
fun isGoogleDriveUri(uri: Uri?): Boolean {
return "com.google.android.apps.docs.storage.legacy" == uri?.authority || "com.google.android.apps.docs.storage" == uri?.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is local.
*/
fun isLocalStorageDocument(uri: Uri?): Boolean {
return AUTHORITY.equals(uri?.authority, true)
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private fun isExternalStorageDocument(uri: Uri?): Boolean {
return "com.android.externalstorage.documents".equals(uri?.authority, true)
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private fun isDownloadsDocument(uri: Uri?): Boolean {
return "com.android.providers.downloads.documents".equals(uri?.authority, true)
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private fun isMediaDocument(uri: Uri?): Boolean {
return "com.android.providers.media.documents".equals(uri?.authority, true)
}
/**
* content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/Android/data/com.xxx.xxx/
*
* @param uri
* @return
*/
private fun isHuaWeiUri(uri: Uri?): Boolean {
return "com.huawei.hidisk.fileprovider".equals(uri?.authority, true)
}
}

@ -20,7 +20,9 @@ import android.database.Cursor
import android.net.Uri
import android.provider.OpenableColumns
import com.arialyy.aria.core.DuaContext
import com.arialyy.aria.orm.entity.BlockRecord
import timber.log.Timber
import java.io.File
import java.io.InputStream
import java.util.Locale
@ -32,6 +34,22 @@ object FileUtils {
RegexOption.IGNORE_CASE
)
fun mergeBlock(blockRecordList: List<BlockRecord>, targetPath: Uri): Boolean {
if (blockRecordList.isEmpty()) {
Timber.e("block record list empty")
return false
}
if (!uriEffective(targetPath)) {
Timber.e("invalid uri: $targetPath")
return false
}
val fileList = arrayListOf<File>()
blockRecordList.forEach {
fileList.add(File(it.blockPath))
}
return FileUtil.mergeFile()
}
/**
* check if url is correct
*/

@ -20,7 +20,7 @@ import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IPool;
import com.arialyy.aria.core.inf.ITaskQueue;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.task.ThreadTaskManager2;
import com.arialyy.aria.core.task.ITask;
import timber.log.Timber;
@ -68,8 +68,8 @@ public abstract class AbsTaskQueue<TASK extends ITask> implements ITaskQueue<TAS
return true;
}
TASK task = getExePool().getTask(taskId);
if (task == null && ThreadTaskManager.getInstance().taskIsRunning(taskId)) {
ThreadTaskManager.getInstance().removeTaskThread(taskId);
if (task == null && ThreadTaskManager2.INSTANCE.taskIsRunning(taskId)) {
ThreadTaskManager2.INSTANCE.stopThreadTask(taskId);
}
return task != null && task.isRunning() && taskExists(taskId);
}
@ -129,7 +129,7 @@ public abstract class AbsTaskQueue<TASK extends ITask> implements ITaskQueue<TAS
task.stop(TaskSchedulerType.TYPE_STOP_NOT_NEXT);
}
}
ThreadTaskManager.getInstance().removeAllThreadTask();
ThreadTaskManager2.INSTANCE.removeAllThreadTask();
getCachePool().clear();
}
@ -163,8 +163,8 @@ public abstract class AbsTaskQueue<TASK extends ITask> implements ITaskQueue<TAS
if (taskIsRunning(task.getTaskId())) {
getCachePool().removeTask(task.getTaskId());
getExePool().removeTask(task.getTaskId());
if (ThreadTaskManager.getInstance().taskIsRunning(task.getTaskId())) {
ThreadTaskManager.getInstance().removeTaskThread(task.getTaskId());
if (ThreadTaskManager2.INSTANCE.taskIsRunning(task.getTaskId())) {
ThreadTaskManager2.INSTANCE.stopThreadTask(task.getTaskId());
}
}
break;

Loading…
Cancel
Save