pull/973/merge
William 3 years ago committed by GitHub
commit ad71105b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/FUNDING.yml
  2. 21
      .github/ISSUE_TEMPLATE/Custom.md
  3. 20
      .github/stale.yml
  4. 15
      AppFrame/build.gradle
  5. 1
      AppFrame/src/main/java/com/arialyy/frame/cache/DiskLruCache.java
  6. 2
      AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java
  7. 3
      AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java
  8. 7
      Aria/bintray-release.gradle
  9. 22
      Aria/build.gradle
  10. 26
      Aria/src/main/java/com/arialyy/aria/core/Aria.java
  11. 303
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  12. 32
      Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java
  13. 2
      Aria/src/main/java/com/arialyy/aria/core/command/AbsCmd.java
  14. 6
      Aria/src/main/java/com/arialyy/aria/core/command/AbsCmdFactory.java
  15. 10
      Aria/src/main/java/com/arialyy/aria/core/command/AbsGroupCmd.java
  16. 28
      Aria/src/main/java/com/arialyy/aria/core/command/AbsNormalCmd.java
  17. 4
      Aria/src/main/java/com/arialyy/aria/core/command/AddCmd.java
  18. 14
      Aria/src/main/java/com/arialyy/aria/core/command/CancelAllCmd.java
  19. 4
      Aria/src/main/java/com/arialyy/aria/core/command/CancelCmd.java
  20. 44
      Aria/src/main/java/com/arialyy/aria/core/command/CmdHelper.java
  21. 6
      Aria/src/main/java/com/arialyy/aria/core/command/CommandManager.java
  22. 2
      Aria/src/main/java/com/arialyy/aria/core/command/DGSubStartCmd.java
  23. 2
      Aria/src/main/java/com/arialyy/aria/core/command/DGSubStopCmd.java
  24. 6
      Aria/src/main/java/com/arialyy/aria/core/command/GroupCmdFactory.java
  25. 11
      Aria/src/main/java/com/arialyy/aria/core/command/HighestPriorityCmd.java
  26. 6
      Aria/src/main/java/com/arialyy/aria/core/command/NormalCmdFactory.java
  27. 8
      Aria/src/main/java/com/arialyy/aria/core/command/ReStartCmd.java
  28. 128
      Aria/src/main/java/com/arialyy/aria/core/command/ResumeAllCmd.java
  29. 200
      Aria/src/main/java/com/arialyy/aria/core/command/ResumeThread.java
  30. 128
      Aria/src/main/java/com/arialyy/aria/core/command/StartCmd.java
  31. 2
      Aria/src/main/java/com/arialyy/aria/core/command/StopAllCmd.java
  32. 4
      Aria/src/main/java/com/arialyy/aria/core/command/StopCmd.java
  33. 35
      Aria/src/main/java/com/arialyy/aria/core/common/AbsBuilderTarget.java
  34. 58
      Aria/src/main/java/com/arialyy/aria/core/common/AbsNormalTarget.java
  35. 51
      Aria/src/main/java/com/arialyy/aria/core/common/BaseDelegate.java
  36. 495
      Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java
  37. 181
      Aria/src/main/java/com/arialyy/aria/core/common/HttpOption.java
  38. 199
      Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java
  39. 99
      Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java
  40. 457
      Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java
  41. 135
      Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java
  42. 219
      Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java
  43. 16
      Aria/src/main/java/com/arialyy/aria/core/common/controller/BuilderController.java
  44. 62
      Aria/src/main/java/com/arialyy/aria/core/common/controller/FeatureController.java
  45. 10
      Aria/src/main/java/com/arialyy/aria/core/common/controller/INormalFeature.java
  46. 48
      Aria/src/main/java/com/arialyy/aria/core/common/controller/NormalController.java
  47. 421
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java
  48. 105
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/FTPSDelegate.java
  49. 92
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpDelegate.java
  50. 102
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskConfig.java
  51. 52
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/SSLSessionReuseFTPSClient.java
  52. 186
      Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java
  53. 91
      Aria/src/main/java/com/arialyy/aria/core/download/CheckDEntityUtil.java
  54. 104
      Aria/src/main/java/com/arialyy/aria/core/download/CheckDGEntityUtil.java
  55. 48
      Aria/src/main/java/com/arialyy/aria/core/download/CheckFtpDirEntityUtil.java
  56. 26
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java
  57. 208
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
  58. 26
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java
  59. 129
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java
  60. 61
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java
  61. 140
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java
  62. 286
      Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java
  63. 147
      Aria/src/main/java/com/arialyy/aria/core/download/group/ChildDLoadListener.java
  64. 188
      Aria/src/main/java/com/arialyy/aria/core/download/group/DGroupUtil.java
  65. 109
      Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java
  66. 104
      Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirInfoThread.java
  67. 65
      Aria/src/main/java/com/arialyy/aria/core/download/group/GroupTargetFactory.java
  68. 193
      Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSchedulers.java
  69. 133
      Aria/src/main/java/com/arialyy/aria/core/download/group/SubDLoadUtil.java
  70. 118
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java
  71. 261
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveLoader.java
  72. 36
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveOption.java
  73. 180
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveUtil.java
  74. 141
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Option.java
  75. 115
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodDelegate.java
  76. 102
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodOption.java
  77. 145
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodUtil.java
  78. 24
      Aria/src/main/java/com/arialyy/aria/core/download/target/AbsGroupConfigHandler.java
  79. 30
      Aria/src/main/java/com/arialyy/aria/core/download/target/DNormalConfigHandler.java
  80. 45
      Aria/src/main/java/com/arialyy/aria/core/download/target/DTargetFactory.java
  81. 49
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java
  82. 26
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirBuilderTarget.java
  83. 11
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirConfigHandler.java
  84. 33
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirNormalTarget.java
  85. 44
      Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java
  86. 53
      Aria/src/main/java/com/arialyy/aria/core/download/target/GroupBuilderTarget.java
  87. 68
      Aria/src/main/java/com/arialyy/aria/core/download/target/GroupNormalTarget.java
  88. 80
      Aria/src/main/java/com/arialyy/aria/core/download/target/HttpBuilderTarget.java
  89. 7
      Aria/src/main/java/com/arialyy/aria/core/download/target/HttpGroupConfigHandler.java
  90. 51
      Aria/src/main/java/com/arialyy/aria/core/download/target/HttpNormalTarget.java
  91. 59
      Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java
  92. 56
      Aria/src/main/java/com/arialyy/aria/core/download/target/TcpBuilderTarget.java
  93. 90
      Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpDelegate.java
  94. 77
      Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpTaskConfig.java
  95. 36
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalTask.java
  96. 121
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsReceiver.java
  97. 20
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java
  98. 2
      Aria/src/main/java/com/arialyy/aria/core/inf/IConfigHandler.java
  99. 8
      Aria/src/main/java/com/arialyy/aria/core/inf/IReceiver.java
  100. 23
      Aria/src/main/java/com/arialyy/aria/core/inf/ReceiverType.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,8 @@
# These are supported funding model platforms
# https://help.github.com/cn/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository
# github: ["AriaLyy"]
# patreon: AriaLyy
# liberapay: Aria
open_collective: Aria
custom: ["https://paypal.me/arialyy"]

@ -1,17 +1,28 @@
--- ---
name: 问题描述 name: 问题描述
about: 请详细描述你遇到的问题 about: 提交问题前,请先阅读文档和搜索issue
--- ---
## Aria版本 <!-- 提交问题前,请先阅读文档和搜索相应问题的issue -->
## 版本
* 框架版本
## 什么问题 * 系统版本
## 错误的url
## 如何复现此问题 ## 错误日志
<!-- 请提供详细的错误日志 -->
## 重现步骤
<!-- 请提供明确的步骤 -->
1.
2.
3.
4.
## 控制台日志

@ -0,0 +1,20 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 7
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 1000
# Issues with these labels will never be considered stale
exemptLabels:
- bug
- adaptation
- new features
- suggest
- pending
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

@ -1,4 +1,6 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
@ -13,6 +15,9 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
debug{
debuggable true
}
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@ -21,6 +26,10 @@ android {
dataBinding { dataBinding {
enabled = true enabled = true
} }
lintOptions {
abortOnError false
}
} }
dependencies { dependencies {
@ -29,11 +38,11 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
api 'androidx.appcompat:appcompat:1.0.2' api "androidx.appcompat:appcompat:${rootProject.ext.XAppcompatVersion}"
api 'com.google.code.gson:gson:2.8.2' api 'com.google.code.gson:gson:2.8.2'
api 'io.reactivex:rxandroid:1.2.0' api 'io.reactivex:rxandroid:1.2.0'
api 'io.reactivex:rxjava:1.1.5' api 'io.reactivex:rxjava:1.1.5'
implementation 'com.squareup.okhttp3:okhttp:3.2.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0' implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
@ -43,6 +52,6 @@ dependencies {
api 'androidx.lifecycle:lifecycle-runtime:2.0.0' api 'androidx.lifecycle:lifecycle-runtime:2.0.0'
api 'androidx.lifecycle:lifecycle-extensions:2.0.0' api 'androidx.lifecycle:lifecycle-extensions:2.0.0'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.0.0' kapt 'androidx.lifecycle:lifecycle-compiler:2.0.0'
} }

@ -245,7 +245,6 @@ public final class DiskLruCache implements Closeable {
/** /**
* Recursively delete everything in {@code dir}. * Recursively delete everything in {@code dir}.
*/ */
// TODO: this should specify paths as Strings rather than as Files
public static void deleteContents(File dir) throws IOException { public static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles(); File[] files = dir.listFiles();
if (files == null) { if (files == null) {

@ -12,7 +12,7 @@ import javax.crypto.spec.SecretKeySpec;
/** /**
* AES加密 * AES加密
* *
* @author lyy * author lyy
*/ */
public class AESEncryption { public class AESEncryption {
/** /**

@ -92,5 +92,6 @@ public class RegularExpression {
/** /**
* IP * IP
*/ */
public static String IP = "\\d+\\.\\d+\\.\\d+\\.\\d+"; public static String IP =
"([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}";
} }

@ -1,10 +1,11 @@
apply plugin: 'com.novoda.bintray-release' apply plugin: 'com.novoda.bintray-release'
publish { publish {
artifactId = 'aria-core' // artifactId = 'aria-core'
// uploadName = 'AriaApi'
artifactId = 'core'
uploadName = 'Core'
userOrg = rootProject.ext.userOrg userOrg = rootProject.ext.userOrg
groupId = rootProject.ext.groupId groupId = rootProject.ext.groupId
// uploadName = rootProject.uploadName
uploadName = 'AriaApi'
publishVersion = rootProject.ext.publishVersion publishVersion = rootProject.ext.publishVersion
desc = rootProject.ext.desc desc = rootProject.ext.desc
website = rootProject.ext.website website = rootProject.ext.website

@ -7,10 +7,13 @@ android {
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 329 versionCode rootProject.ext.versionCode
versionName "3.2.9" versionName rootProject.ext.versionName
} }
buildTypes { buildTypes {
debug{
debuggable true
}
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@ -20,14 +23,17 @@ android {
abortOnError false abortOnError false
} }
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
implementation 'androidx.appcompat:appcompat:1.0.0' // implementation "androidx.appcompat:appcompat:${rootProject.ext.XAppcompatVersion}"
api project(':AriaAnnotations') api project(path: ':AriaAnnotations')
api 'com.arialyy.aria:aria-ftp-plug:1.0.4' // api project(path: ':PublicComponent')
api project(path: ':HttpComponent')
}
// compile project(':AriaFtpPlug') //apply from: 'bintray-release.gradle'
ext{
PUBLISH_ARTIFACT_ID = 'core'
} }
apply from: 'bintray-release.gradle' apply from: '../gradle/mavenCentral-release.gradle'

@ -24,11 +24,10 @@ import android.app.Service;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.arialyy.aria.core.download.DownloadReceiver; import com.arialyy.aria.core.download.DownloadReceiver;
import com.arialyy.aria.core.upload.UploadReceiver; import com.arialyy.aria.core.upload.UploadReceiver;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/** /**
* Created by lyy on 2016/12/1. * Created by lyy on 2016/12/1.
@ -132,21 +131,20 @@ import com.arialyy.aria.util.ALog;
private static Context convertContext(Object obj) { private static Context convertContext(Object obj) {
if (obj instanceof Application) { if (obj instanceof Application) {
return (Application) obj; return (Application) obj;
} else if (obj instanceof Service) { }
if (obj instanceof Service) {
return (Service) obj; return (Service) obj;
} else if (obj instanceof Activity) { }
if (obj instanceof Activity) {
return (Activity) obj; return (Activity) obj;
} else if (obj instanceof DialogFragment) { }
return ((DialogFragment) obj).getContext(); if (CommonUtil.isFragment(obj.getClass())) {
} else if (obj instanceof android.app.DialogFragment) { return CommonUtil.getFragmentActivity(obj);
return ((android.app.DialogFragment) obj).getActivity(); }
} else if (obj instanceof Fragment) { if (obj instanceof Dialog) {
return ((Fragment) obj).getContext();
} else if (obj instanceof android.app.Fragment) {
return ((android.app.Fragment) obj).getActivity();
} else if (obj instanceof Dialog) {
return ((Dialog) obj).getContext(); return ((Dialog) obj).getContext();
} else if (obj instanceof PopupWindow) { }
if (obj instanceof PopupWindow) {
return ((PopupWindow) obj).getContentView().getContext(); return ((PopupWindow) obj).getContentView().getContext();
} }
ALog.e("Aria", "请使用download(this)或upload(this)"); ALog.e("Aria", "请使用download(this)或upload(this)");

@ -19,34 +19,23 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.util.Log;
import android.os.Looper;
import android.widget.PopupWindow;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.arialyy.aria.core.command.CommandManager; import com.arialyy.aria.core.command.CommandManager;
import com.arialyy.aria.core.common.QueueMod; import com.arialyy.aria.core.common.QueueMod;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.config.AppConfig; import com.arialyy.aria.core.config.AppConfig;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.config.DGroupConfig; import com.arialyy.aria.core.config.DGroupConfig;
import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.config.DownloadConfig;
import com.arialyy.aria.core.config.UploadConfig; import com.arialyy.aria.core.config.UploadConfig;
import com.arialyy.aria.core.config.XMLReader;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.download.DownloadReceiver; import com.arialyy.aria.core.download.DownloadReceiver;
import com.arialyy.aria.core.inf.AbsReceiver; import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.inf.IReceiver; import com.arialyy.aria.core.inf.IReceiver;
import com.arialyy.aria.core.inf.ReceiverType; import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.loader.IRecordHandler;
import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UploadReceiver; import com.arialyy.aria.core.upload.UploadReceiver;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
@ -54,54 +43,34 @@ import com.arialyy.aria.orm.DelegateWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.AriaCrashHandler; import com.arialyy.aria.util.AriaCrashHandler;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DeleteURecord;
import com.arialyy.aria.util.RecordUtil; import com.arialyy.aria.util.RecordUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.lang.reflect.Method;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
/** /**
* Created by lyy on 2016/12/1. https://github.com/AriaLyy/Aria Aria管理器,任务操作在这里执行 * Created by lyy on 2016/12/1. https://github.com/AriaLyy/Aria
* Aria管理器任务操作在这里执行
*/ */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager { @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager {
private static final String TAG = "AriaManager"; private static final String TAG = "AriaManager";
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
public static final String DOWNLOAD_TEMP_DIR = "/Aria/temp/download/";
public static final String UPLOAD_TEMP_DIR = "/Aria/temp/upload/";
/**
* 是否已经联网true 已经联网
*/
private static boolean isConnectedNet = true;
@SuppressLint("StaticFieldLeak") private static volatile AriaManager INSTANCE = null; @SuppressLint("StaticFieldLeak") private static volatile AriaManager INSTANCE = null;
private Map<String, AbsReceiver> mReceivers = new ConcurrentHashMap<>(); private Map<String, AbsReceiver> mReceivers = new ConcurrentHashMap<>();
/**
* activity 和其DialogFragment的映射表
*/
private Map<String, List<String>> mSubClass = new ConcurrentHashMap<>();
private static Context APP; private static Context APP;
private DownloadConfig mDConfig;
private UploadConfig mUConfig;
private AppConfig mAConfig;
private DGroupConfig mDGConfig;
private Handler mAriaHandler;
private DelegateWrapper mDbWrapper; private DelegateWrapper mDbWrapper;
private AriaConfig mConfig;
private AriaManager(Context context) { private AriaManager(Context context) {
APP = context.getApplicationContext(); APP = context.getApplicationContext();
} }
public static AriaManager getInstance() { public static AriaManager getInstance() {
if (INSTANCE == null) {
throw new NullPointerException("请使用AriaManager.init(context)初始化管理器");
}
return INSTANCE; return INSTANCE;
} }
@ -118,56 +87,16 @@ import org.xml.sax.SAXException;
} }
private void initData() { private void initData() {
mConfig = AriaConfig.init(APP);
initDb(APP); initDb(APP);
regAppLifeCallback(APP); regAppLifeCallback(APP);
initConfig();
initAria(); initAria();
amendTaskState();
regNetCallBack(APP);
} }
public Context getAPP() { public Context getAPP() {
return APP; return APP;
} }
/**
* 注册网络监听只有配置了检查网络{@link AppConfig#isNetCheck()}才会注册事件
*/
private void regNetCallBack(Context context) {
if (!getAppConfig().isNetCheck()) {
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return;
}
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() {
@Override public void onLost(Network network) {
super.onLost(network);
isConnectedNet = false;
ALog.d(TAG, "onLost");
}
@Override public void onAvailable(Network network) {
super.onAvailable(network);
ALog.d(TAG, "onAvailable");
isConnectedNet = true;
}
});
}
/** /**
* 初始化数据库 * 初始化数据库
*/ */
@ -183,13 +112,15 @@ import org.xml.sax.SAXException;
} }
} }
mDbWrapper = DelegateWrapper.init(context.getApplicationContext()); mDbWrapper = DelegateWrapper.init(context.getApplicationContext());
amendTaskState();
} }
private void initAria() { private void initAria() {
if (mAConfig.getUseAriaCrashHandler()) { AppConfig appConfig = mConfig.getAConfig();
if (appConfig.getUseAriaCrashHandler()) {
Thread.setDefaultUncaughtExceptionHandler(new AriaCrashHandler()); Thread.setDefaultUncaughtExceptionHandler(new AriaCrashHandler());
} }
mAConfig.setLogLevel(mAConfig.getLogLevel()); appConfig.setLogLevel(appConfig.getLogLevel());
CommandManager.init(); CommandManager.init();
} }
@ -210,17 +141,6 @@ import org.xml.sax.SAXException;
} }
} }
public synchronized Handler getAriaHandler() {
if (mAriaHandler == null) {
mAriaHandler = new Handler(Looper.getMainLooper());
}
return mAriaHandler;
}
public boolean isConnectedNet() {
return isConnectedNet;
}
public Map<String, AbsReceiver> getReceiver() { public Map<String, AbsReceiver> getReceiver() {
return mReceivers; return mReceivers;
} }
@ -237,7 +157,7 @@ import org.xml.sax.SAXException;
* @deprecated 后续版本会删除该api * @deprecated 后续版本会删除该api
*/ */
@Deprecated public AriaManager setUploadQueueMod(QueueMod mod) { @Deprecated public AriaManager setUploadQueueMod(QueueMod mod) {
mUConfig.setQueueMod(mod.tag); mConfig.getUConfig().setQueueMod(mod.tag);
return this; return this;
} }
@ -253,7 +173,7 @@ import org.xml.sax.SAXException;
* @deprecated 后续版本会删除该api * @deprecated 后续版本会删除该api
*/ */
@Deprecated public AriaManager setDownloadQueueMod(QueueMod mod) { @Deprecated public AriaManager setDownloadQueueMod(QueueMod mod) {
mDConfig.setQueueMod(mod.tag); mConfig.getDConfig().setQueueMod(mod.tag);
return this; return this;
} }
@ -267,7 +187,7 @@ import org.xml.sax.SAXException;
* </pre> * </pre>
*/ */
public DownloadConfig getDownloadConfig() { public DownloadConfig getDownloadConfig() {
return mDConfig; return mConfig.getDConfig();
} }
/** /**
@ -280,14 +200,14 @@ import org.xml.sax.SAXException;
* </pre> * </pre>
*/ */
public UploadConfig getUploadConfig() { public UploadConfig getUploadConfig() {
return mUConfig; return mConfig.getUConfig();
} }
/** /**
* 获取APP配置 * 获取APP配置
*/ */
public AppConfig getAppConfig() { public AppConfig getAppConfig() {
return mAConfig; return mConfig.getAConfig();
} }
/** /**
@ -300,13 +220,14 @@ import org.xml.sax.SAXException;
* </pre> * </pre>
*/ */
public DGroupConfig getDGroupConfig() { public DGroupConfig getDGroupConfig() {
return mDGConfig; return mConfig.getDGConfig();
} }
/** /**
* 处理下载操作 * 处理下载操作
*/ */
DownloadReceiver download(Object obj) { DownloadReceiver download(Object obj) {
IReceiver receiver = mReceivers.get(getKey(ReceiverType.DOWNLOAD, obj)); IReceiver receiver = mReceivers.get(getKey(ReceiverType.DOWNLOAD, obj));
if (receiver == null) { if (receiver == null) {
receiver = putReceiver(ReceiverType.DOWNLOAD, obj); receiver = putReceiver(ReceiverType.DOWNLOAD, obj);
@ -335,101 +256,30 @@ import org.xml.sax.SAXException;
public void delRecord(int type, String key, boolean removeFile) { public void delRecord(int type, String key, boolean removeFile) {
switch (type) { switch (type) {
case 1: // 删除普通任务记录 case 1: // 删除普通任务记录
RecordUtil.delTaskRecord(key, RecordHandler.TYPE_DOWNLOAD, removeFile, true); RecordUtil.delTaskRecord(key, IRecordHandler.TYPE_DOWNLOAD, removeFile, true);
break; break;
case 2: case 2:
RecordUtil.delGroupTaskRecord(key, removeFile); RecordUtil.delGroupTaskRecordByHash(key, removeFile);
break; break;
case 3: case 3:
RecordUtil.delTaskRecord(key, RecordHandler.TYPE_UPLOAD); DeleteURecord.getInstance().deleteRecord(key, removeFile, true);
break; break;
} }
} }
private IReceiver putReceiver(@ReceiverType String type, Object obj) { private IReceiver putReceiver(ReceiverType type, Object obj) {
final String key = getKey(type, obj); final String key = getKey(type, obj);
IReceiver receiver = mReceivers.get(key); IReceiver receiver = mReceivers.get(key);
boolean needRmReceiver = false;
// 监控Dialog、fragment、popupWindow的生命周期
final WidgetLiftManager widgetLiftManager = new WidgetLiftManager();
if (obj instanceof Dialog) {
needRmReceiver = widgetLiftManager.handleDialogLift((Dialog) obj);
} else if (obj instanceof PopupWindow) {
needRmReceiver = widgetLiftManager.handlePopupWindowLift((PopupWindow) obj);
} else if (obj instanceof DialogFragment) {
needRmReceiver = widgetLiftManager.handleDialogFragmentLift((DialogFragment) obj);
} else if (obj instanceof android.app.DialogFragment) {
needRmReceiver = widgetLiftManager.handleDialogFragmentLift((android.app.DialogFragment) obj);
}
if (receiver == null) { if (receiver == null) {
AbsReceiver absReceiver; AbsReceiver absReceiver =
switch (type) { type.equals(ReceiverType.DOWNLOAD) ? new DownloadReceiver(obj) : new UploadReceiver(obj);
case ReceiverType.DOWNLOAD:
absReceiver = new DownloadReceiver();
break;
case ReceiverType.UPLOAD:
absReceiver = new UploadReceiver();
break;
default:
absReceiver = new DownloadReceiver();
break;
}
absReceiver.targetName = obj.getClass().getName();
AbsReceiver.OBJ_MAP.put(absReceiver.getKey(), obj);
absReceiver.needRmListener = needRmReceiver;
mReceivers.put(key, absReceiver); mReceivers.put(key, absReceiver);
receiver = absReceiver; receiver = absReceiver;
} }
return receiver; return receiver;
} }
/**
* 根据功能类型和控件类型获取对应的key
*
* @param type {@link ReceiverType}
* @param obj 观察者对象
* @return {@link #createKey(String, Object)}
*/
private String getKey(@ReceiverType String type, Object obj) {
if (obj instanceof DialogFragment) {
relateSubClass(type, obj, ((DialogFragment) obj).getActivity());
} else if (obj instanceof android.app.DialogFragment) {
relateSubClass(type, obj, ((android.app.DialogFragment) obj).getActivity());
} else if (obj instanceof Fragment) {
relateSubClass(type, obj, ((Fragment) obj).getActivity());
} else if (obj instanceof android.app.Fragment) {
relateSubClass(type, obj, ((android.app.Fragment) obj).getActivity());
} else if (obj instanceof Dialog) {
Activity activity = ((Dialog) obj).getOwnerActivity();
if (activity != null) {
relateSubClass(type, obj, activity);
}
} else if (obj instanceof PopupWindow) {
Context context = ((PopupWindow) obj).getContentView().getContext();
if (context instanceof Activity) {
relateSubClass(type, obj, (Activity) context);
}
}
return createKey(type, obj);
}
/**
* 关联Activity类和Fragment间的关系
*
* @param sub Frgament或dialog类
* @param activity activity寄主类
*/
private void relateSubClass(@ReceiverType String type, Object sub, Activity activity) {
String key = createKey(type, activity);
List<String> list = mSubClass.get(key);
if (list == null) {
list = new ArrayList<>();
mSubClass.put(key, list);
}
list.add(createKey(type, sub));
}
/** /**
* 根据功能类型和控件类型获取对应的key * 根据功能类型和控件类型获取对应的key
* *
@ -438,60 +288,8 @@ import org.xml.sax.SAXException;
* @return key的格式为{@code String.format("%s_%s_%s", obj.getClass().getName(), type, * @return key的格式为{@code String.format("%s_%s_%s", obj.getClass().getName(), type,
* obj.hashCode());} * obj.hashCode());}
*/ */
private String createKey(@ReceiverType String type, Object obj) { private String getKey(ReceiverType type, Object obj) {
return String.format("%s_%s_%s", obj.getClass().getName(), type, obj.hashCode()); return String.format("%s_%s_%s", CommonUtil.getTargetName(obj), type.name(), obj.hashCode());
}
/**
* 初始化配置文件
*/
private void initConfig() {
mDConfig = Configuration.getInstance().downloadCfg;
mUConfig = Configuration.getInstance().uploadCfg;
mAConfig = Configuration.getInstance().appCfg;
mDGConfig = Configuration.getInstance().dGroupCfg;
File xmlFile = new File(APP.getFilesDir().getPath() + Configuration.XML_FILE);
File tempDir = new File(APP.getFilesDir().getPath() + "/temp");
if (!xmlFile.exists()) {
loadConfig();
} else {
try {
String md5Code = CommonUtil.getFileMD5(xmlFile);
File file = new File(APP.getFilesDir().getPath() + "/temp.xml");
if (file.exists()) {
file.delete();
}
CommonUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"),
file.getPath());
if (!CommonUtil.checkMD5(md5Code, file) || !Configuration.getInstance().configExists()) {
loadConfig();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (tempDir.exists()) {
File newDir = new File(APP.getFilesDir().getPath() + DOWNLOAD_TEMP_DIR);
newDir.mkdirs();
tempDir.renameTo(newDir);
}
}
/**
* 加载配置文件
*/
private void loadConfig() {
try {
XMLReader helper = new XMLReader();
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(APP.getAssets().open("aria_config.xml"), helper);
CommonUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"),
APP.getFilesDir().getPath() + Configuration.XML_FILE);
} catch (ParserConfigurationException | IOException | SAXException e) {
ALog.e(TAG, e.toString());
}
} }
/** /**
@ -513,40 +311,47 @@ import org.xml.sax.SAXException;
ALog.e(TAG, "target obj is null"); ALog.e(TAG, "target obj is null");
return; return;
} }
List<String> temp = new ArrayList<>();
// 移除寄主的receiver // 移除寄主的receiver
for (Iterator<Map.Entry<String, AbsReceiver>> iter = mReceivers.entrySet().iterator(); for (Iterator<Map.Entry<String, AbsReceiver>> iter = mReceivers.entrySet().iterator();
iter.hasNext(); ) { iter.hasNext(); ) {
Map.Entry<String, AbsReceiver> entry = iter.next(); Map.Entry<String, AbsReceiver> entry = iter.next();
String key = entry.getKey(); String key = entry.getKey();
if (key.equals(getKey(ReceiverType.DOWNLOAD, obj)) || key.equals( AbsReceiver receiver = entry.getValue();
getKey(ReceiverType.UPLOAD, obj))) {
AbsReceiver receiver = mReceivers.get(key); if (receiver.isFragment()){
List<String> subNames = mSubClass.get(key); Method method = CommonUtil.getMethod(receiver.obj.getClass(), "getActivity");
if (subNames != null && !subNames.isEmpty()) { if (method != null){
temp.addAll(subNames); try {
} Activity ac = (Activity) method.invoke(receiver.obj);
if (receiver != null) { if (ac == obj){
receiver.destroy(); receiver.destroy();
}
iter.remove(); iter.remove();
continue;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} }
} }
// 移除寄生的receiver // 处理内部类的
if (!temp.isEmpty()) { String objClsName = obj.getClass().getName();
for (Iterator<Map.Entry<String, AbsReceiver>> iter = mReceivers.entrySet().iterator(); if (receiver.isLocalOrAnonymousClass && key.startsWith(objClsName)) {
iter.hasNext(); ) {
Map.Entry<String, AbsReceiver> entry = iter.next();
if (temp.contains(entry.getKey())) {
AbsReceiver receiver = mReceivers.get(entry.getKey());
if (receiver != null) {
receiver.destroy(); receiver.destroy();
}
iter.remove(); iter.remove();
continue;
} }
if (key.equals(getKey(ReceiverType.DOWNLOAD, obj))
|| key.equals(getKey(ReceiverType.UPLOAD, obj))
) {
receiver.destroy();
iter.remove();
} }
} }
Log.d(TAG, "debug");
} }
/** /**

@ -21,7 +21,6 @@ import android.content.DialogInterface;
import android.os.Build; import android.os.Build;
import android.os.Message; import android.os.Message;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import androidx.fragment.app.DialogFragment;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -30,33 +29,20 @@ import java.lang.reflect.Field;
* Created by lyy on 2017/2/7. * Created by lyy on 2017/2/7.
* 为组件添加生命周期 * 为组件添加生命周期
*/ */
final class WidgetLiftManager { public final class WidgetLiftManager {
private final String TAG = "WidgetLiftManager"; private final String TAG = "WidgetLiftManager";
/** /**
* 处理DialogFragment事件 * 处理DialogFragment事件
*
* @param dialogFragment {@link android.app.DialogFragment}
*/ */
@TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift( @TargetApi(Build.VERSION_CODES.HONEYCOMB) public boolean handleDialogFragmentLift(Dialog dialog) {
android.app.DialogFragment dialogFragment) { return handleDialogLift(dialog);
return handleDialogLift(dialogFragment.getDialog());
}
/**
* 处理DialogFragment事件
*
* @param dialogFragment {@link DialogFragment}
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift(
DialogFragment dialogFragment) {
return handleDialogLift(dialogFragment.getDialog());
} }
/** /**
* 处理悬浮框取消或dismiss事件 * 处理悬浮框取消或dismiss事件
*/ */
boolean handlePopupWindowLift(PopupWindow popupWindow) { public boolean handlePopupWindowLift(PopupWindow popupWindow) {
try { try {
Field dismissField = CommonUtil.getField(popupWindow.getClass(), "mOnDismissListener"); Field dismissField = CommonUtil.getField(popupWindow.getClass(), "mOnDismissListener");
PopupWindow.OnDismissListener listener = PopupWindow.OnDismissListener listener =
@ -87,8 +73,16 @@ final class WidgetLiftManager {
/** /**
* 处理对话框取消或dismiss * 处理对话框取消或dismiss
*
* @return true 设置了dialog的销毁事件false 没有设置dialog的销毁事件
*/ */
boolean handleDialogLift(Dialog dialog) { public boolean handleDialogLift(Dialog dialog) {
if (dialog == null) {
ALog.w(TAG,
"dialog 为空,没有设置自动销毁事件,为了防止内存泄露,请在dismiss方法中调用Aria.download(this).unRegister();来注销事件\n"
+ "如果你使用的是DialogFragment,那么你需要在onDestroy()中进行销毁Aria事件操作");
return false;
}
try { try {
Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage"); Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage");
Message dismissMsg = (Message) dismissField.get(dialog); Message dismissMsg = (Message) dismissField.get(dialog);

@ -16,7 +16,7 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.queue.AbsTaskQueue; import com.arialyy.aria.core.queue.AbsTaskQueue;
/** /**

@ -15,7 +15,8 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.
@ -25,8 +26,7 @@ public abstract class AbsCmdFactory<TASK_ENTITY extends AbsTaskWrapper, CMD exte
/** /**
* @param entity 下载实体 * @param entity 下载实体
* @param taskType {@link ICmd#TASK_TYPE_DOWNLOAD}{@link ICmd#TASK_TYPE_DOWNLOAD_GROUP}{@link * {@link ITask#DOWNLOAD}{@link ITask#DOWNLOAD_GROUP}{@link ITask#UPLOAD}
* ICmd#TASK_TYPE_UPLOAD}
*/ */
public abstract CMD createCmd(TASK_ENTITY entity, int type, int taskType); public abstract CMD createCmd(TASK_ENTITY entity, int type, int taskType);
} }

@ -15,11 +15,11 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.inf.AbsGroupTask; import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.inf.AbsGroupTaskWrapper; import com.arialyy.aria.core.task.AbsGroupTask;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -39,7 +39,7 @@ public abstract class AbsGroupCmd<T extends AbsGroupTaskWrapper> extends AbsCmd<
mTaskWrapper = entity; mTaskWrapper = entity;
TAG = CommonUtil.getClassName(this); TAG = CommonUtil.getClassName(this);
if (entity instanceof DGTaskWrapper) { if (entity instanceof DGTaskWrapper) {
mQueue = DownloadGroupTaskQueue.getInstance(); mQueue = DGroupTaskQueue.getInstance();
isDownloadCmd = true; isDownloadCmd = true;
} }
} }

@ -18,14 +18,14 @@ package com.arialyy.aria.core.command;
import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.ITask; import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.queue.DownloadTaskQueue; import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.queue.UploadTaskQueue; import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.core.scheduler.ISchedulers; import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -54,19 +54,19 @@ public abstract class AbsNormalCmd<T extends AbsTaskWrapper> extends AbsCmd<T> {
ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD"); ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD");
return; return;
} }
mQueue = DownloadTaskQueue.getInstance(); mQueue = DTaskQueue.getInstance();
} else if (taskType == ITask.DOWNLOAD_GROUP) { } else if (taskType == ITask.DOWNLOAD_GROUP) {
if (!(entity instanceof DGTaskWrapper)) { if (!(entity instanceof DGTaskWrapper)) {
ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD_GROUP"); ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD_GROUP");
return; return;
} }
mQueue = DownloadGroupTaskQueue.getInstance(); mQueue = DGroupTaskQueue.getInstance();
} else if (taskType == ITask.UPLOAD) { } else if (taskType == ITask.UPLOAD) {
if (!(entity instanceof UTaskWrapper)) { if (!(entity instanceof UTaskWrapper)) {
ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_UPLOAD"); ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_UPLOAD");
return; return;
} }
mQueue = UploadTaskQueue.getInstance(); mQueue = UTaskQueue.getInstance();
} else { } else {
ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD、TASK_TYPE_DOWNLOAD_GROUP、TASK_TYPE_UPLOAD"); ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD、TASK_TYPE_DOWNLOAD_GROUP、TASK_TYPE_UPLOAD");
return; return;
@ -157,14 +157,6 @@ public abstract class AbsNormalCmd<T extends AbsTaskWrapper> extends AbsCmd<T> {
mQueue.resumeTask(task); mQueue.resumeTask(task);
} }
/**
* 启动指定任务
*
* @param task 指定任务
*/
void startTask(AbsTask task) {
mQueue.startTask(task);
}
/** /**
* 从队列中获取任务 * 从队列中获取任务

@ -16,8 +16,8 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;

@ -20,11 +20,11 @@ import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.queue.DownloadTaskQueue; import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.queue.UploadTaskQueue; import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
@ -104,11 +104,11 @@ final public class CancelAllCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T
return; return;
} }
if (te instanceof DTaskWrapper) { if (te instanceof DTaskWrapper) {
mQueue = DownloadTaskQueue.getInstance(); mQueue = DTaskQueue.getInstance();
} else if (te instanceof UTaskWrapper) { } else if (te instanceof UTaskWrapper) {
mQueue = UploadTaskQueue.getInstance(); mQueue = UTaskQueue.getInstance();
} else if (te instanceof DGTaskWrapper) { } else if (te instanceof DGTaskWrapper) {
mQueue = DownloadGroupTaskQueue.getInstance(); mQueue = DGroupTaskQueue.getInstance();
} }
te.setRemoveFile(removeFile); te.setRemoveFile(removeFile);
removeTask(te); removeTask(te);

@ -16,8 +16,8 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/** /**
* Created by lyy on 2016/9/20. * Created by lyy on 2016/9/20.

@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.command;
import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
public class CmdHelper {
/**
* 创建任务命令
*
* @param taskType {@link ITask#DOWNLOAD}{@link ITask#DOWNLOAD_GROUP}{@link ITask#UPLOAD}
*/
public static <T extends AbsTaskWrapper> AbsNormalCmd createNormalCmd(T entity, int cmd,
int taskType) {
return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType);
}
/**
* 创建任务组命令
*
* @param childUrl 子任务url
*/
public static <T extends AbsGroupTaskWrapper> AbsGroupCmd createGroupCmd(T entity, int cmd,
String childUrl) {
return GroupCmdFactory.getInstance().createCmd(entity, cmd, childUrl);
}
}

@ -30,7 +30,7 @@ public class CommandManager {
EventMsgUtil.getDefault().register(this); EventMsgUtil.getDefault().register(this);
} }
public static CommandManager init() { public static void init() {
if (instance == null) { if (instance == null) {
synchronized (CommandManager.class) { synchronized (CommandManager.class) {
if (instance == null) { if (instance == null) {
@ -38,7 +38,6 @@ public class CommandManager {
} }
} }
} }
return instance;
} }
@Event @Event
@ -51,9 +50,6 @@ public class CommandManager {
@Event @Event
public void start(StartCmd cmd) { public void start(StartCmd cmd) {
if (CommonUtil.isFastDoubleClick()) {
return;
}
cmd.executeCmd(); cmd.executeCmd();
} }

@ -15,7 +15,7 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsGroupTaskWrapper; import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.

@ -15,7 +15,7 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsGroupTaskWrapper; import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.

@ -15,7 +15,7 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsGroupTaskWrapper; import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
/** /**
* Created by AriaL on 2017/6/29. 任务组子任务控制命令 * Created by AriaL on 2017/6/29. 任务组子任务控制命令
@ -46,13 +46,11 @@ public class GroupCmdFactory {
} }
/** /**
* @param target 创建任务的对象
* @param wrapper 参数信息 * @param wrapper 参数信息
* @param type 命令类型{@link #SUB_TASK_START}{@link #SUB_TASK_STOP} * @param type 命令类型{@link #SUB_TASK_START}{@link #SUB_TASK_STOP}
* @param childUrl 需要控制的子任务url * @param childUrl 需要控制的子任务url
*/ */
public AbsGroupCmd createCmd(String target, AbsGroupTaskWrapper wrapper, int type, public AbsGroupCmd createCmd(AbsGroupTaskWrapper wrapper, int type, String childUrl) {
String childUrl) {
AbsGroupCmd cmd = null; AbsGroupCmd cmd = null;
switch (type) { switch (type) {
case SUB_TASK_START: case SUB_TASK_START:

@ -15,10 +15,11 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.DownloadTask; import com.arialyy.aria.core.task.DownloadTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.queue.DownloadTaskQueue; import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.NetUtils; import com.arialyy.aria.util.NetUtils;
@ -41,7 +42,7 @@ final class HighestPriorityCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T>
@Override public void executeCmd() { @Override public void executeCmd() {
if (!canExeCmd) return; if (!canExeCmd) return;
if (!NetUtils.isConnected(AriaManager.getInstance().getAPP())){ if (!NetUtils.isConnected(AriaConfig.getInstance().getAPP())){
ALog.e(TAG, "启动任务失败,网络未连接"); ALog.e(TAG, "启动任务失败,网络未连接");
return; return;
} }
@ -50,7 +51,7 @@ final class HighestPriorityCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T>
task = (DownloadTask) createTask(); task = (DownloadTask) createTask();
} }
if (task != null) { if (task != null) {
((DownloadTaskQueue) mQueue).setTaskHighestPriority(task); ((DTaskQueue) mQueue).setTaskHighestPriority(task);
} }
} }
} }

@ -16,7 +16,8 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/** /**
* Created by Lyy on 2016/9/23. * Created by Lyy on 2016/9/23.
@ -83,8 +84,7 @@ public class NormalCmdFactory extends AbsCmdFactory<AbsTaskWrapper, AbsNormalCmd
* @param entity 下载实体 * @param entity 下载实体
* @param type 命令类型{@link #TASK_CREATE}{@link #TASK_START}{@link #TASK_CANCEL}{@link * @param type 命令类型{@link #TASK_CREATE}{@link #TASK_START}{@link #TASK_CANCEL}{@link
* #TASK_STOP}{@link #TASK_HIGHEST_PRIORITY}{@link #TASK_STOP_ALL}{@link #TASK_RESUME_ALL} * #TASK_STOP}{@link #TASK_HIGHEST_PRIORITY}{@link #TASK_STOP_ALL}{@link #TASK_RESUME_ALL}
* @param taskType {@link ICmd#TASK_TYPE_DOWNLOAD}{@link ICmd#TASK_TYPE_DOWNLOAD_GROUP}{@link * @param taskType {@link ITask#DOWNLOAD}{@link ITask#DOWNLOAD_GROUP}{@link ITask#UPLOAD}
* ICmd#TASK_TYPE_UPLOAD}
*/ */
public AbsNormalCmd createCmd(AbsTaskWrapper entity, int type, int taskType) { public AbsNormalCmd createCmd(AbsTaskWrapper entity, int type, int taskType) {
switch (type) { switch (type) {

@ -15,9 +15,9 @@
*/ */
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.TaskSchedulerType; import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/** /**
* 重新开始任务命令 * 重新开始任务命令
@ -34,8 +34,8 @@ final class ReStartCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> {
task = createTask(); task = createTask();
} }
if (task != null) { if (task != null) {
task.cancel(TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY); mQueue.cancelTask(task, TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY);
task.start(TaskSchedulerType.TYPE_START_AND_RESET_STATE); mQueue.startTask(task, TaskSchedulerType.TYPE_START_AND_RESET_STATE);
} }
} }
} }

@ -1,25 +1,25 @@
/*
* 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.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaConfig;
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.DownloadGroupEntity;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
import com.arialyy.aria.core.queue.DownloadTaskQueue;
import com.arialyy.aria.core.queue.UploadTaskQueue;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.NetUtils; import com.arialyy.aria.util.NetUtils;
import java.util.ArrayList;
import java.util.List;
/** /**
* Created by AriaL on 2017/6/13. * Created by AriaL on 2017/6/13.
@ -30,105 +30,17 @@ import java.util.List;
* 4.恢复下载的任务规则是停止时间越晚的任务启动越早按照DESC来进行排序 * 4.恢复下载的任务规则是停止时间越晚的任务启动越早按照DESC来进行排序
*/ */
final class ResumeAllCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> { final class ResumeAllCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> {
private List<AbsTaskWrapper> mWaitList = new ArrayList<>();
ResumeAllCmd(T entity, int taskType) { ResumeAllCmd(T entity, int taskType) {
super(entity, taskType); super(entity, taskType);
} }
@Override public void executeCmd() { @Override public void executeCmd() {
if (!NetUtils.isConnected(AriaManager.getInstance().getAPP())) { if (!NetUtils.isConnected(AriaConfig.getInstance().getAPP())) {
ALog.w(TAG, "恢复任务失败,网络未连接"); ALog.w(TAG, "恢复任务失败,网络未连接");
return; return;
} }
if (isDownloadCmd) { new Thread(new ResumeThread(isDownloadCmd,
findTaskData(1); String.format("state!=%s", IEntity.STATE_COMPLETE))).start();
findTaskData(2);
} else {
findTaskData(3);
}
resumeWaitTask();
}
/**
* 查找数据库中的所有任务数据
*
* @param type {@code 1}单任务下载任务{@code 2}任务组下载任务{@code 3} 单任务上传任务
*/
private void findTaskData(int type) {
if (type == 1) {
List<DownloadEntity> entities =
DbEntity.findDatas(DownloadEntity.class,
"isGroupChild=? AND state!=? ORDER BY stopTime DESC", "false", "1");
if (entities != null && !entities.isEmpty()) {
for (DownloadEntity entity : entities) {
addResumeEntity(TaskWrapperManager.getInstance()
.getNormalTaskWrapper(DTaskWrapper.class, entity.getId()));
}
}
} else if (type == 2) {
List<DownloadGroupEntity> entities =
DbEntity.findDatas(DownloadGroupEntity.class, "state!=? ORDER BY stopTime DESC", "1");
if (entities != null && !entities.isEmpty()) {
for (DownloadGroupEntity entity : entities) {
addResumeEntity(
TaskWrapperManager.getInstance()
.getGroupWrapper(DGTaskWrapper.class, entity.getId()));
}
}
} else if (type == 3) {
List<UploadEntity> entities =
DbEntity.findDatas(UploadEntity.class, "state!=? ORDER BY stopTime DESC", "1");
if (entities != null && !entities.isEmpty()) {
for (UploadEntity entity : entities) {
addResumeEntity(TaskWrapperManager.getInstance()
.getNormalTaskWrapper(UTaskWrapper.class, entity.getId()));
}
}
}
}
/**
* 添加恢复实体
*/
private void addResumeEntity(AbsTaskWrapper te) {
if (te == null || te.getEntity() == null) {
return;
}
if (!mQueue.taskExists(te.getKey())) {
mWaitList.add(te);
}
}
/**
* 处理等待状态的任务
*/
private void resumeWaitTask() {
int maxTaskNum = mQueue.getMaxTaskNum();
if (mWaitList == null || mWaitList.isEmpty()) {
return;
}
List<AbsEntity> resumeEntities = new ArrayList<>();
for (AbsTaskWrapper te : mWaitList) {
if (te instanceof DTaskWrapper) {
mQueue = DownloadTaskQueue.getInstance();
} else if (te instanceof UTaskWrapper) {
mQueue = UploadTaskQueue.getInstance();
} else if (te instanceof DGTaskWrapper) {
mQueue = DownloadGroupTaskQueue.getInstance();
}
if (mQueue.getCurrentExePoolNum() < maxTaskNum) {
startTask(createTask(te));
} else {
te.getEntity().setState(IEntity.STATE_WAIT);
AbsTask task = createTask(te);
sendWaitState(task);
resumeEntities.add(te.getEntity());
}
}
if (!resumeEntities.isEmpty()) {
DbEntity.updateManyData(resumeEntities);
}
} }
} }

@ -0,0 +1,200 @@
/*
* 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.command;
import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsEntity;
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.DownloadGroupEntity;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.AbsTaskQueue;
import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
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;
/**
* 恢复任务工具
*/
public class ResumeThread implements Runnable {
private String TAG = CommonUtil.getClassName(getClass());
private List<AbsTaskWrapper> mWaitList = new ArrayList<>();
private boolean isDownloadCmd;
private String sqlCondition;
ResumeThread(boolean isDownload, String sqlCondition) {
this.isDownloadCmd = isDownload;
this.sqlCondition = sqlCondition;
}
/**
* 查找数据库中的所有任务数据
*
* @param type {@code 1}单任务下载任务{@code 2}任务组下载任务{@code 3} 单任务上传任务
*/
private void findTaskData(int type) {
if (type == 1) {
List<DownloadEntity> entities =
DbEntity.findDatas(DownloadEntity.class,
String.format("NOT(isGroupChild) AND NOT(isComplete) AND %s ORDER BY stopTime DESC",
sqlCondition));
if (entities != null && !entities.isEmpty()) {
for (DownloadEntity entity : entities) {
addResumeEntity(TaskWrapperManager.getInstance()
.getNormalTaskWrapper(DTaskWrapper.class, entity.getId()));
}
}
} else if (type == 2) {
List<DownloadGroupEntity> entities =
DbEntity.findDatas(DownloadGroupEntity.class,
String.format("NOT(isComplete) AND %s ORDER BY stopTime DESC",
sqlCondition));
if (entities != null && !entities.isEmpty()) {
for (DownloadGroupEntity entity : entities) {
addResumeEntity(
TaskWrapperManager.getInstance()
.getGroupWrapper(DGTaskWrapper.class, entity.getId()));
}
}
} else if (type == 3) {
List<UploadEntity> entities =
DbEntity.findDatas(UploadEntity.class,
String.format("NOT(isComplete) AND %s ORDER BY stopTime DESC",
sqlCondition));
if (entities != null && !entities.isEmpty()) {
for (UploadEntity entity : entities) {
addResumeEntity(TaskWrapperManager.getInstance()
.getNormalTaskWrapper(UTaskWrapper.class, entity.getId()));
}
}
}
}
/**
* 添加恢复实体
*/
private void addResumeEntity(AbsTaskWrapper te) {
if (te == null || te.getEntity() == null || TextUtils.isEmpty(te.getKey())) {
return;
}
mWaitList.add(te);
}
/**
* 处理等待状态的任务
*/
private void resumeWaitTask() {
if (mWaitList == null || mWaitList.isEmpty()) {
return;
}
List<AbsEntity> resumeEntities = new ArrayList<>();
for (AbsTaskWrapper wrapper : mWaitList) {
AbsTaskQueue queue = null;
if (wrapper instanceof DTaskWrapper) {
queue = DTaskQueue.getInstance();
} else if (wrapper instanceof UTaskWrapper) {
queue = UTaskQueue.getInstance();
} else if (wrapper instanceof DGTaskWrapper) {
queue = DGroupTaskQueue.getInstance();
}
if (queue == null) {
ALog.e(TAG, "任务类型错误");
continue;
}
if (wrapper.getEntity() == null || TextUtils.isEmpty(wrapper.getKey())) {
ALog.e(TAG, "任务实体为空或key为空");
continue;
}
AbsTask task = queue.getTask(wrapper.getKey());
if (task != null) {
ALog.w(TAG, "任务已存在");
continue;
}
int maxTaskNum = queue.getMaxTaskNum();
task = queue.createTask(wrapper);
if (task == null) {
continue;
}
handleWrapper(wrapper);
if (queue.getCurrentExePoolNum() < maxTaskNum) {
queue.startTask(task);
} else {
wrapper.getEntity().setState(IEntity.STATE_WAIT);
sendWaitState(task);
resumeEntities.add(wrapper.getEntity());
}
}
if (!resumeEntities.isEmpty()) {
DbEntity.updateManyData(resumeEntities);
}
}
/**
* 处理ftp的wrapper
*/
private void handleWrapper(AbsTaskWrapper wrapper) {
int requestType = wrapper.getRequestType();
if (requestType == ITaskWrapper.D_FTP
|| requestType == ITaskWrapper.U_FTP
|| requestType == ITaskWrapper.D_FTP_DIR) {
wrapper.getOptionParams()
.setParams(IOptionConstant.ftpUrlEntity,
CommonUtil.getFtpUrlInfo(wrapper.getEntity().getKey()));
}
}
/**
* 发送等待状态
*/
private void sendWaitState(AbsTask task) {
if (task != null) {
task.getTaskWrapper().setState(IEntity.STATE_WAIT);
task.getOutHandler().obtainMessage(ISchedulers.WAIT, task).sendToTarget();
}
}
@Override public void run() {
if (isDownloadCmd) {
findTaskData(1);
findTaskData(2);
} else {
findTaskData(3);
}
resumeWaitTask();
}
}

@ -16,51 +16,47 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.common.QueueMod; import com.arialyy.aria.core.common.QueueMod;
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.DownloadGroupEntity;
import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
import com.arialyy.aria.core.queue.DownloadTaskQueue;
import com.arialyy.aria.core.queue.UploadTaskQueue;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.NetUtils; import com.arialyy.aria.util.NetUtils;
import java.util.ArrayList;
import java.util.List;
/** /**
* Created by lyy on 2016/8/22. 开始命令 队列模型{@link QueueMod#NOW}{@link QueueMod#WAIT} * Created by lyy on 2016/8/22. 开始命令 队列模型{@link QueueMod#NOW}{@link QueueMod#WAIT}
*/ */
final class StartCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> { final public class StartCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> {
private boolean nowStart = false;
StartCmd(T entity, int taskType) { StartCmd(T entity, int taskType) {
super(entity, taskType); super(entity, taskType);
} }
/**
* 立即执行任务
*
* @param nowStart true 立即执行任务无论执行队列是否满了
*/
public void setNowStart(boolean nowStart) {
this.nowStart = nowStart;
}
@Override public void executeCmd() { @Override public void executeCmd() {
if (!canExeCmd) return; if (!canExeCmd) return;
if (!NetUtils.isConnected(AriaManager.getInstance().getAPP())) { if (!NetUtils.isConnected(AriaConfig.getInstance().getAPP())) {
ALog.e(TAG, "启动任务失败,网络未连接"); ALog.e(TAG, "启动任务失败,网络未连接");
return; return;
} }
String mod; String mod;
int maxTaskNum = mQueue.getMaxTaskNum(); int maxTaskNum = mQueue.getMaxTaskNum();
AriaManager manager = AriaManager.getInstance(); AriaConfig config = AriaConfig.getInstance();
if (isDownloadCmd) { if (isDownloadCmd) {
mod = manager.getDownloadConfig().getQueueMod(); mod = config.getDConfig().getQueueMod();
} else { } else {
mod = manager.getUploadConfig().getQueueMod(); mod = config.getUConfig().getQueueMod();
} }
AbsTask task = getTask(); AbsTask task = getTask();
@ -85,14 +81,30 @@ final class StartCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> {
ALog.d(TAG, String.format("开始新任务, 任务状态:%s", state)); ALog.d(TAG, String.format("开始新任务, 任务状态:%s", state));
startTask(); startTask();
} }
} else {
if (nowStart) {
startTask();
} else { } else {
sendWaitState(task); sendWaitState(task);
} }
} }
}
} else { } else {
//任务没执行并且执行队列中没有该任务,才认为任务没有运行中 //任务没执行并且执行队列中没有该任务,才认为任务没有运行中
if (!mQueue.taskIsRunning(task.getKey())) { if (!mQueue.taskIsRunning(task.getKey())) {
if (mod.equals(QueueMod.NOW.getTag())) {
resumeTask(); resumeTask();
} else {
if (mQueue.getCurrentExePoolNum() < maxTaskNum) {
resumeTask();
} else {
if (nowStart) {
resumeTask();
} else {
sendWaitState(task);
}
}
}
} else { } else {
ALog.w(TAG, String.format("任务【%s】已经在运行", task.getTaskName())); ALog.w(TAG, String.format("任务【%s】已经在运行", task.getTaskName()));
} }
@ -106,73 +118,7 @@ final class StartCmd<T extends AbsTaskWrapper> extends AbsNormalCmd<T> {
* 当缓冲队列为null时查找数据库中所有等待中的任务 * 当缓冲队列为null时查找数据库中所有等待中的任务
*/ */
private void findAllWaitTask() { private void findAllWaitTask() {
new Thread(new WaitTaskThread()).start(); new Thread(
} new ResumeThread(isDownloadCmd, String.format("state=%s", IEntity.STATE_WAIT))).start();
private class WaitTaskThread implements Runnable {
@Override public void run() {
if (isDownloadCmd) {
handleTask(findWaitData(1));
handleTask(findWaitData(2));
} else {
handleTask(findWaitData(3));
}
}
private List<AbsTaskWrapper> findWaitData(int type) {
List<AbsTaskWrapper> waitList = new ArrayList<>();
TaskWrapperManager tManager = TaskWrapperManager.getInstance();
if (type == 1) { // 普通下载任务
List<DownloadEntity> dEntities = DbEntity.findDatas(DownloadEntity.class,
"isGroupChild=? and state=?", "false", "3");
if (dEntities != null && !dEntities.isEmpty()) {
for (DownloadEntity e : dEntities) {
waitList.add(tManager.getNormalTaskWrapper(DTaskWrapper.class, e.getId()));
}
}
} else if (type == 2) { // 组合任务
List<DownloadGroupEntity> dEntities =
DbEntity.findDatas(DownloadGroupEntity.class, "state=?", "3");
if (dEntities != null && !dEntities.isEmpty()) {
for (DownloadGroupEntity e : dEntities) {
if (e.getTaskType() == ITaskWrapper.DG_HTTP) {
waitList.add(tManager.getGroupWrapper(DGTaskWrapper.class, e.getId()));
} else if (e.getTaskType() == ITaskWrapper.D_FTP_DIR) {
waitList.add(tManager.getGroupWrapper(DGTaskWrapper.class, e.getId()));
}
}
}
} else if (type == 3) { //普通上传任务
List<UploadEntity> dEntities = DbEntity.findDatas(UploadEntity.class, "state=?", "3");
if (dEntities != null && !dEntities.isEmpty()) {
for (UploadEntity e : dEntities) {
waitList.add(tManager.getNormalTaskWrapper(UTaskWrapper.class, e.getId()));
}
}
}
return waitList;
}
private void handleTask(List<AbsTaskWrapper> waitList) {
for (AbsTaskWrapper te : waitList) {
if (te.getEntity() == null) continue;
AbsTask task = getTask(te.getKey());
if (task != null) continue;
if (te instanceof DTaskWrapper) {
if (te.getRequestType() == ITaskWrapper.D_FTP
|| te.getRequestType() == ITaskWrapper.U_FTP) {
te.asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(te.getEntity().getKey()));
}
mQueue = DownloadTaskQueue.getInstance();
} else if (te instanceof UTaskWrapper) {
mQueue = UploadTaskQueue.getInstance();
} else if (te instanceof DGTaskWrapper) {
mQueue = DownloadGroupTaskQueue.getInstance();
}
createTask(te);
}
}
} }
} }

@ -1,6 +1,6 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/** /**
* Created by AriaL on 2017/6/13. * Created by AriaL on 2017/6/13.

@ -16,8 +16,8 @@
package com.arialyy.aria.core.command; package com.arialyy.aria.core.command;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;

@ -15,18 +15,25 @@
*/ */
package com.arialyy.aria.core.common; package com.arialyy.aria.core.common;
import com.arialyy.aria.core.common.controller.IStartFeature;
import com.arialyy.aria.core.common.controller.BuilderController; import com.arialyy.aria.core.common.controller.BuilderController;
import com.arialyy.aria.core.common.controller.IStartFeature;
import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTarget;
/** /**
* 处理第一次下载 * 处理第一次创建的任务
*/ */
public abstract class AbsBuilderTarget<TARGET extends AbsBuilderTarget> extends AbsTarget<TARGET> public abstract class AbsBuilderTarget<TARGET extends AbsBuilderTarget> extends AbsTarget<TARGET>
implements IStartFeature { implements IStartFeature {
private BuilderController mStartController; private BuilderController mStartController;
/**
* 任务操作前调用
*/
protected void onPre() {
}
private synchronized BuilderController getController() { private synchronized BuilderController getController() {
if (mStartController == null) { if (mStartController == null) {
mStartController = new BuilderController(getTaskWrapper()); mStartController = new BuilderController(getTaskWrapper());
@ -34,23 +41,42 @@ public abstract class AbsBuilderTarget<TARGET extends AbsBuilderTarget> extends
return mStartController; return mStartController;
} }
/**
* 是否忽略权限检查
*/
public TARGET ignoreCheckPermissions() {
getController().ignoreCheckPermissions();
return (TARGET) this;
}
/**
* 忽略文件占用不管文件路径是否被其它任务占用都执行上传\下载任务
* 需要注意的是如果文件被其它任务占用并且还调用了该方法将自动删除占用了该文件路径的任务
*/
public TARGET ignoreFilePathOccupy() {
getController().ignoreFilePathOccupy();
return (TARGET) this;
}
/** /**
* 添加任务 * 添加任务
* *
* @return 正常添加返回任务id否则返回-1 * @return 添加成功返回任务id创建失败返回-1
*/ */
@Override @Override
public long add() { public long add() {
onPre();
return getController().add(); return getController().add();
} }
/** /**
* 开始任务 * 开始任务
* *
* @return 正常启动返回任务id否则返回-1 * @return 创建成功返回任务id创建失败返回-1
*/ */
@Override @Override
public long create() { public long create() {
onPre();
return getController().create(); return getController().create();
} }
@ -65,6 +91,7 @@ public abstract class AbsBuilderTarget<TARGET extends AbsBuilderTarget> extends
*/ */
@Override @Override
public long setHighestPriority() { public long setHighestPriority() {
onPre();
return getController().setHighestPriority(); return getController().setHighestPriority();
} }
} }

@ -18,12 +18,12 @@ package com.arialyy.aria.core.common;
import com.arialyy.aria.core.common.controller.INormalFeature; import com.arialyy.aria.core.common.controller.INormalFeature;
import com.arialyy.aria.core.common.controller.NormalController; import com.arialyy.aria.core.common.controller.NormalController;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DeleteDGRecord;
import com.arialyy.aria.util.RecordUtil; import com.arialyy.aria.util.RecordUtil;
/** /**
@ -32,19 +32,38 @@ import com.arialyy.aria.util.RecordUtil;
public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends AbsTarget<TARGET> public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends AbsTarget<TARGET>
implements INormalFeature { implements INormalFeature {
/**
* 任务操作前调用
*/
protected void onPre() {
}
/**
* 是否忽略权限检查
*/
public TARGET ignoreCheckPermissions() {
getController().ignoreCheckPermissions();
return (TARGET) this;
}
/** /**
* 任务是否在执行 * 任务是否在执行
* *
* @return {@code true} 任务正在执行 * @return {@code true} 任务正在执行
*/ */
public abstract boolean isRunning(); public boolean isRunning() {
return false;
}
/** /**
* 任务是否存在 * 任务是否存在
* *
* @return {@code true} 任务存在 * @return {@code true} 任务存在
*/ */
public abstract boolean taskExists(); public boolean taskExists() {
return false;
}
private NormalController mNormalController; private NormalController mNormalController;
@ -64,11 +83,10 @@ public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends Ab
cancel(); cancel();
} else { } else {
if (getEntity() instanceof AbsNormalEntity) { if (getEntity() instanceof AbsNormalEntity) {
RecordUtil.delTaskRecord((AbsNormalEntity) getEntity(), getTaskWrapper().isRemoveFile()); RecordUtil.delNormalTaskRecord((AbsNormalEntity) getEntity(), getTaskWrapper().isRemoveFile());
} else if (getEntity() instanceof DownloadGroupEntity) { } else if (getEntity() instanceof DownloadGroupEntity) {
RecordUtil.delGroupTaskRecord(((DownloadGroupEntity) getEntity()), DeleteDGRecord.getInstance()
getTaskWrapper().isRemoveFile(), .deleteRecord(getEntity(), getTaskWrapper().isRemoveFile(), true);
true);
} }
TaskWrapperManager.getInstance().removeTaskWrapper(getTaskWrapper()); TaskWrapperManager.getInstance().removeTaskWrapper(getTaskWrapper());
} }
@ -139,6 +157,7 @@ public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends Ab
*/ */
@Override @Override
public void stop() { public void stop() {
onPre();
getController().stop(); getController().stop();
} }
@ -147,7 +166,18 @@ public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends Ab
*/ */
@Override @Override
public void resume() { public void resume() {
getController().resume(); resume(false);
}
/**
* 正常来说当执行队列满时调用恢复任务接口只能将任务放到缓存队列中
* 如果希望调用恢复接口马上进入执行队列需要使用该方法
*
* @param newStart true 立即将任务恢复到执行队列中
*/
@Override public void resume(boolean newStart) {
onPre();
getController().resume(newStart);
} }
/** /**
@ -155,7 +185,7 @@ public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends Ab
*/ */
@Override @Override
public void cancel() { public void cancel() {
getController().cancel(); cancel(false);
} }
/** /**
@ -163,17 +193,19 @@ public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends Ab
*/ */
@Override @Override
public void reTry() { public void reTry() {
onPre();
getController().reTry(); getController().reTry();
} }
/** /**
* 删除任务 * 删除任务
* *
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经删除完成的文件 * @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经完成的文件
* {@code false}如果任务已经完成只删除任务数据库记录 * {@code false}如果任务已经完成只删除任务数据库记录
*/ */
@Override @Override
public void cancel(boolean removeFile) { public void cancel(boolean removeFile) {
onPre();
getController().cancel(removeFile); getController().cancel(removeFile);
} }
@ -181,12 +213,14 @@ public abstract class AbsNormalTarget<TARGET extends AbsNormalTarget> extends Ab
* 重新下载 * 重新下载
*/ */
@Override @Override
public void reStart() { public long reStart() {
getController().reStart(); onPre();
return getController().reStart();
} }
@Override @Override
public void save() { public void save() {
onPre();
getController().save(); getController().save();
} }
} }

@ -1,51 +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.common;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.controller.ControllerType;
import com.arialyy.aria.core.common.controller.FeatureController;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.util.CommonUtil;
public abstract class BaseDelegate<TARGET extends AbsTarget> {
protected final String TAG;
protected TARGET mTarget;
protected AbsTaskWrapper mWrapper;
public BaseDelegate(TARGET target, AbsTaskWrapper wrapper) {
TAG = CommonUtil.getClassName(getClass());
mTarget = target;
mWrapper = wrapper;
}
protected AbsTaskWrapper getTaskWrapper() {
return mWrapper;
}
/**
* 使用对应等控制器注意
* 1对于不存在的任务第一次下载只能使用{@link ControllerType#CREATE_CONTROLLER}
* 2对于已存在的任务只能使用{@link ControllerType#TASK_CONTROLLER}
*
* @param clazz {@link ControllerType#CREATE_CONTROLLER}{@link ControllerType#TASK_CONTROLLER}
*/
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public synchronized <T extends FeatureController> T controller(@ControllerType Class<T> clazz) {
return FeatureController.newInstance(clazz, getTaskWrapper());
}
}

@ -0,0 +1,495 @@
/*
* 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;
import android.text.TextUtils;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.IdEntity;
import com.arialyy.aria.core.ProtocolType;
import com.arialyy.aria.core.processor.IFtpUploadInterceptor;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import java.text.DateFormatSymbols;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
/**
* Created by laoyuyu on 2018/3/9.
*/
public class FtpOption extends BaseOption {
private String charSet, userName, password, account;
private boolean isNeedLogin = false;
private FtpUrlEntity urlEntity;
private String protocol;
private boolean isImplicit = true;
private IFtpUploadInterceptor uploadInterceptor;
private int connMode = FtpConnectionMode.DATA_CONNECTION_MODE_PASV;
private int minPort, maxPort;
private String activeExternalIPAddress;
//---------------- ftp client 配置信息 start
private String defaultDateFormatStr = null;
private String recentDateFormatStr = null;
private String serverLanguageCode = null;
private String shortMonthNames = null;
private String serverTimeZoneId = null;
private String systemKey = FTPServerIdentifier.SYST_UNIX;
//---------------- ftp client 配置信息 end
private IdEntity idEntity = new IdEntity();
public FtpOption() {
super();
}
public FtpOption charSet(String charSet) {
if (TextUtils.isEmpty(charSet)) {
throw new NullPointerException("字符编码为空");
}
this.charSet = charSet;
return this;
}
public FtpOption login(String userName, String password) {
return login(userName, password, null);
}
public FtpOption login(String userName, String password, String account) {
if (TextUtils.isEmpty(userName)) {
ALog.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
ALog.e(TAG, "密码不能为null");
return this;
}
this.userName = userName;
this.password = password;
this.account = account;
isNeedLogin = true;
return this;
}
/**
* 设置协议类型
*
* @param protocol {@link ProtocolType}
*/
public FtpOption setProtocol(String protocol) {
if (TextUtils.isEmpty(protocol)) {
ALog.e(TAG, "设置协议失败,协议信息为空");
return this;
}
this.protocol = protocol;
return this;
}
/**
* 设置私钥证书别名
*
* @param keyAlias 别名
*/
public FtpOption setAlias(String keyAlias) {
if (TextUtils.isEmpty(keyAlias)) {
ALog.e(TAG, "设置证书别名失败,证书别名为空");
return this;
}
idEntity.keyAlias = keyAlias;
return this;
}
/**
* 设置ca证书密码
*
* @param storePass ca证书密码
*/
public FtpOption setStorePass(String storePass) {
if (TextUtils.isEmpty(storePass)) {
ALog.e(TAG, "设置证书密码失败,证书密码为空");
return this;
}
idEntity.storePass = storePass;
return this;
}
/**
* 设置cer证书路径
*
* @param storePath 证书路径
*/
public FtpOption setStorePath(String storePath) {
if (TextUtils.isEmpty(storePath)) {
ALog.e(TAG, "设置证书路径失败,证书路径为空");
return this;
}
idEntity.storePath = storePath;
return this;
}
/**
* 设置安全模式默认true
*
* @param isImplicit true 隐式false 显式
*/
public FtpOption setImplicit(boolean isImplicit) {
this.isImplicit = isImplicit;
return this;
}
/**
* FTP文件上传拦截器如果远端已有同名文件可使用该拦截器控制覆盖文件或修改该文件上传到服务器端端文件名
*/
public FtpOption setUploadInterceptor(IFtpUploadInterceptor uploadInterceptor) {
if (uploadInterceptor == null) {
throw new NullPointerException("ftp拦截器为空");
}
CheckUtil.checkMemberClass(uploadInterceptor.getClass());
this.uploadInterceptor = uploadInterceptor;
return this;
}
/**
* 设置数据传输模式默认为被动模式
* 主动模式传输文件时客户端开启一个端口ftp服务器连接到客户端的该端口ftp服务器推送数据到客户端
* 被动模式传输文件时ftp服务器开启一个端口客户端连接到ftp服务器的这个端口客户端请求ftp服务器的数据
* 请注意主动模式是服务器主动连接到android如果使用住的模式请确保ftp服务器能ping通android
*
* @param connMode {@link FtpConnectionMode#DATA_CONNECTION_MODE_PASV},
* {@link FtpConnectionMode#DATA_CONNECTION_MODE_ACTIVITY}
*/
public FtpOption setConnectionMode(int connMode) {
if (connMode != FtpConnectionMode.DATA_CONNECTION_MODE_PASV
&& connMode != FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY) {
ALog.e(TAG, "连接模式设置失败,默认启用被动模式");
return this;
}
this.connMode = connMode;
return this;
}
/**
* 主动模式下的端口范围
*/
public FtpOption setActivePortRange(int minPort, int maxPort) {
if (minPort > maxPort) {
ALog.e(TAG, "设置端口范围错误,minPort > maxPort");
return this;
}
if (minPort <= 0 || minPort >= 65535) {
ALog.e(TAG, "端口范围错误");
return this;
}
if (maxPort >= 65535) {
ALog.e(TAG, "端口范围错误");
return this;
}
this.minPort = minPort;
this.maxPort = maxPort;
return this;
}
/**
* 主动模式下对外ip可被Ftp服务器访问的ip
*/
public FtpOption setActiveExternalIPAddress(String ip) {
if (TextUtils.isEmpty(ip)) {
ALog.e(TAG, "ip为空");
return this;
}
if (!CheckUtil.checkIp(ip)) {
ALog.e(TAG, "ip地址错误:" + ip);
return this;
}
this.activeExternalIPAddress = ip;
return this;
}
/**
* 设置ftp服务器所在的操作系统的标志如果出现文件获取失败请设置该标志为
* 默认使用{@link FTPServerIdentifier#SYST_UNIX}
*
* @param identifier {@link FTPServerIdentifier}
*/
public FtpOption setServerIdentifier(String identifier) {
this.systemKey = identifier;
return this;
}
/**
* 解析ftp信息时默认的文件日期格式setDefaultDateFormatStr("d MMM yyyy")
*
* @param defaultDateFormatStr 日期格式
*/
public FtpOption setDefaultDateFormatStr(String defaultDateFormatStr) {
this.defaultDateFormatStr = defaultDateFormatStr;
return this;
}
/**
* 解析ftp信息时默认的文件修改日期格式setRecentDateFormatStr("d MMM HH:mm")
*
* @param recentDateFormatStr 日期格式
*/
public FtpOption setRecentDateFormatStr(String recentDateFormatStr) {
this.recentDateFormatStr = recentDateFormatStr;
return this;
}
/**
* 设置服务器使用的时区java.util.TimeZoneAmerica/Chicago or Asia/Rangoon.
*
* @param serverTimeZoneId 时区
*/
public void setServerTimeZoneId(String serverTimeZoneId) {
this.serverTimeZoneId = serverTimeZoneId;
}
/**
* <p>
* setter for the shortMonthNames property.
* This property allows the user to specify a set of month names
* used by the server that is different from those that may be
* specified using the {@link #setServerLanguageCode(String) serverLanguageCode}
* property.
* </p><p>
* This should be a string containing twelve strings each composed of
* three characters, delimited by pipe (|) characters. Currently,
* only 8-bit ASCII characters are known to be supported. For example,
* a set of month names used by a hypothetical Icelandic FTP server might
* conceivably be specified as
* <code>"jan|feb|mar|apr|ma&#xED;|j&#xFA;n|j&#xFA;l|&#xE1;g&#xFA;|sep|okt|n&#xF3;v|des"</code>.
* </p>
*
* @param shortMonthNames The value to set to the shortMonthNames property.
*/
public void setShortMonthNames(String shortMonthNames) {
this.shortMonthNames = shortMonthNames;
}
/**
* 设置服务器语言代码
*
* @param serverLanguageCode {@link #LANGUAGE_CODE_MAP}
*/
public FtpOption setServerLanguageCode(String serverLanguageCode) {
this.serverLanguageCode = serverLanguageCode;
return this;
}
public void setUrlEntity(FtpUrlEntity urlEntity) {
this.urlEntity = urlEntity;
urlEntity.needLogin = isNeedLogin;
urlEntity.user = userName;
urlEntity.password = password;
urlEntity.account = account;
urlEntity.idEntity = idEntity;
if (!TextUtils.isEmpty(idEntity.storePath) || !TextUtils.isEmpty(idEntity.prvKey)) {
urlEntity.isFtps = true;
urlEntity.protocol = protocol;
urlEntity.isImplicit = isImplicit;
}
}
/**
* ftp 服务器所在的操作系统标志
*/
public interface FTPServerIdentifier {
/**
* Identifier by which a unix-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_UNIX = "UNIX";
/**
* Identifier for alternate UNIX parser; same as {@link #SYST_UNIX} but leading spaces are
* trimmed from file names. This is to maintain backwards compatibility with
* the original behaviour of the parser which ignored multiple spaces between the date
* and the start of the file name.
*
* @since 3.4
*/
String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM";
/**
* Identifier by which a vms-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_VMS = "VMS";
/**
* Identifier by which a WindowsNT-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_NT = "WINDOWS";
/**
* Identifier by which an OS/2-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_OS2 = "OS/2";
/**
* Identifier by which an OS/400-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_OS400 = "OS/400";
/**
* Identifier by which an AS/400-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_AS400 = "AS/400";
/**
* Identifier by which an MVS-based ftp server is known throughout
* the commons-net ftp system.
*/
String SYST_MVS = "MVS";
/**
* Some servers return an "UNKNOWN Type: L8" message
* in response to the SYST command. We set these to be a Unix-type system.
* This may happen if the ftpd in question was compiled without system
* information.
*
* NET-230 - Updated to be UPPERCASE so that the check done in
* createFileEntryParser will succeed.
*
* @since 1.5
*/
String SYST_L8 = "TYPE: L8";
/**
* Identifier by which an Netware-based ftp server is known throughout
* the commons-net ftp system.
*
* @since 1.5
*/
String SYST_NETWARE = "NETWARE";
/**
* Identifier by which a Mac pre OS-X -based ftp server is known throughout
* the commons-net ftp system.
*
* @since 3.1
*/
// Full string is "MACOS Peter's Server"; the substring below should be enough
String SYST_MACOS_PETER = "MACOS PETER"; // NET-436
}
/**
* 支持的语言代码
*/
private static final Map<String, Object> LANGUAGE_CODE_MAP = new TreeMap<>();
static {
// if there are other commonly used month name encodings which
// correspond to particular locales, please add them here.
// many locales code short names for months as all three letters
// these we handle simply.
LANGUAGE_CODE_MAP.put("en", Locale.ENGLISH);
LANGUAGE_CODE_MAP.put("de", Locale.GERMAN);
LANGUAGE_CODE_MAP.put("it", Locale.ITALIAN);
LANGUAGE_CODE_MAP.put("es", new Locale("es", "", "")); // spanish
LANGUAGE_CODE_MAP.put("pt", new Locale("pt", "", "")); // portuguese
LANGUAGE_CODE_MAP.put("da", new Locale("da", "", "")); // danish
LANGUAGE_CODE_MAP.put("sv", new Locale("sv", "", "")); // swedish
LANGUAGE_CODE_MAP.put("no", new Locale("no", "", "")); // norwegian
LANGUAGE_CODE_MAP.put("nl", new Locale("nl", "", "")); // dutch
LANGUAGE_CODE_MAP.put("ro", new Locale("ro", "", "")); // romanian
LANGUAGE_CODE_MAP.put("sq", new Locale("sq", "", "")); // albanian
LANGUAGE_CODE_MAP.put("sh", new Locale("sh", "", "")); // serbo-croatian
LANGUAGE_CODE_MAP.put("sk", new Locale("sk", "", "")); // slovak
LANGUAGE_CODE_MAP.put("sl", new Locale("sl", "", "")); // slovenian
// some don't
LANGUAGE_CODE_MAP.put("fr",
"jan|f\u00e9v|mar|avr|mai|jun|jui|ao\u00fb|sep|oct|nov|d\u00e9c"); //french
}
/**
* Looks up the supplied language code in the internally maintained table of
* language codes. Returns a DateFormatSymbols object configured with
* short month names corresponding to the code. If there is no corresponding
* entry in the table, the object returned will be that for
* <code>Locale.US</code>
*
* @param languageCode See {@link #setServerLanguageCode(String) serverLanguageCode}
* @return a DateFormatSymbols object configured with short month names
* corresponding to the supplied code, or with month names for
* <code>Locale.US</code> if there is no corresponding entry in the internal
* table.
*/
public static DateFormatSymbols lookupDateFormatSymbols(String languageCode) {
Object lang = LANGUAGE_CODE_MAP.get(languageCode);
if (lang != null) {
if (lang instanceof Locale) {
return new DateFormatSymbols((Locale) lang);
} else if (lang instanceof String) {
return getDateFormatSymbols((String) lang);
}
}
return new DateFormatSymbols(Locale.US);
}
/**
* Returns a DateFormatSymbols object configured with short month names
* as in the supplied string
*
* @param shortmonths This should be as described in
* {@link #setShortMonthNames(String) shortMonthNames}
* @return a DateFormatSymbols object configured with short month names
* as in the supplied string
*/
public static DateFormatSymbols getDateFormatSymbols(String shortmonths) {
String[] months = splitShortMonthString(shortmonths);
DateFormatSymbols dfs = new DateFormatSymbols(Locale.US);
dfs.setShortMonths(months);
return dfs;
}
private static String[] splitShortMonthString(String shortmonths) {
StringTokenizer st = new StringTokenizer(shortmonths, "|");
int monthcnt = st.countTokens();
if (12 != monthcnt) {
throw new IllegalArgumentException("expecting a pipe-delimited string containing 12 tokens");
}
String[] months = new String[13];
int pos = 0;
while (st.hasMoreTokens()) {
months[pos++] = st.nextToken();
}
months[pos] = "";
return months;
}
/**
* Returns a Collection of all the language codes currently supported
* by this class. See {@link #setServerLanguageCode(String) serverLanguageCode}
* for a functional descrption of language codes within this system.
*
* @return a Collection of all the language codes currently supported
* by this class
*/
public static Collection<String> getSupportedLanguageCodes() {
return LANGUAGE_CODE_MAP.keySet();
}
}

@ -0,0 +1,181 @@
/*
* 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;
import android.text.TextUtils;
import com.arialyy.aria.core.processor.IHttpFileLenAdapter;
import com.arialyy.aria.core.processor.IHttpFileNameAdapter;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP任务设置
*/
public class HttpOption extends BaseOption {
private Map<String, String> params;
private Map<String, String> headers;
private RequestEnum requestEnum = RequestEnum.GET;
private Map<String, String> formFields;
private Proxy proxy;
private boolean useServerFileName = false;
private IHttpFileLenAdapter fileLenAdapter;
private IHttpFileNameAdapter fileNameAdapter;
private String attachment;
public HttpOption() {
super();
}
/**
* 设置请求类型
*
* @param requestEnum {@link RequestEnum}
*/
public HttpOption setRequestType(RequestEnum requestEnum) {
this.requestEnum = requestEnum;
return this;
}
/**
* 设置http请求参数
*/
public HttpOption setParams(Map<String, String> params) {
if (this.params == null) {
this.params = new HashMap<>();
}
this.params.putAll(params);
return this;
}
/**
* 设置http请求参数
*/
public HttpOption setParam(String key, String value) {
if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) {
ALog.d(TAG, "key 或value 为空");
return this;
}
if (params == null) {
params = new HashMap<>();
}
params.put(key, value);
return this;
}
/**
* 设置http表单字段
*/
public HttpOption setFormFields(Map<String, String> formFields) {
this.formFields = formFields;
return this;
}
/**
* 设置文件上传需要的key
*
* @param attachment 如果为空默认为"file"
*/
public HttpOption setAttachment(String attachment) {
if (TextUtils.isEmpty(attachment)) {
attachment = "file";
}
this.attachment = attachment;
return this;
}
/**
* 给url请求添加Header数据
* 如果新的header数据和数据保存的不一致则更新数据库中对应的header数据
*
* @param key header对应的key
* @param value header对应的value
*/
public HttpOption addHeader(String key, String value) {
if (TextUtils.isEmpty(key)) {
ALog.w(TAG, "设置header失败,header对应的key不能为null");
return this;
} else if (TextUtils.isEmpty(value)) {
ALog.w(TAG, "设置header失败,header对应的value不能为null");
return this;
}
if (this.headers == null) {
this.headers = new HashMap<>();
}
this.headers.put(key, value);
return this;
}
/**
* 给url请求添加一组header数据
* 如果新的header数据和数据保存的不一致则更新数据库中对应的header数据
*
* @param headers 一组http header数据
*/
public HttpOption addHeaders(Map<String, String> headers) {
if (headers.size() == 0) {
ALog.w(TAG, "设置header失败,map没有header数据");
return this;
}
if (this.headers == null) {
this.headers = new HashMap<>();
}
this.headers.putAll(headers);
return this;
}
/**
* 设置代理
*/
public HttpOption setUrlProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* 是否使用服务器通过content-disposition传递的文件名内容格式{@code attachment;filename=***}
* 如果获取不到服务器文件名则使用用户设置的文件名
*
* @param use {@code true} 使用
*/
public HttpOption useServerFileName(boolean use) {
this.useServerFileName = use;
return this;
}
/**
* 如果你需要使用header中特定的key来设置文件长度或有定制文件长度的需要那么你可以通过该方法自行处理文件长度
*/
public HttpOption setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) {
if (fileLenAdapter == null) {
throw new IllegalArgumentException("adapter为空");
}
CheckUtil.checkMemberClass(fileLenAdapter.getClass());
this.fileLenAdapter = fileLenAdapter;
return this;
}
public HttpOption setFilNameAdapter(IHttpFileNameAdapter fileNameAdapter) {
if (fileNameAdapter == null) {
throw new IllegalArgumentException("adapter为空");
}
CheckUtil.checkMemberClass(fileNameAdapter.getClass());
this.fileNameAdapter = fileNameAdapter;
return this;
}
}

@ -1,199 +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.common;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.download.BaseDListener;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.util.ALog;
import java.io.File;
public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
extends AbsFileer<ENTITY, TASK_WRAPPER> {
private ThreadStateManager mStateManager;
private Handler mStateHandler;
protected int mTotalThreadNum; //总线程数
private int mStartThreadNum; //启动的线程数
protected NormalFileer(IEventListener listener, TASK_WRAPPER wrapper) {
super(listener, wrapper);
EventMsgUtil.getDefault().register(this);
}
/**
* 处理新任务
*
* @return {@code true}创建新任务成功
*/
protected abstract boolean handleNewTask();
/**
* 选择单任务线程的类型
*/
protected abstract AbsThreadTask selectThreadTask(SubThreadConfig<TASK_WRAPPER> config);
/**
* 设置最大下载/上传速度
*
* @param maxSpeed 单位为kb
*/
protected void setMaxSpeed(int maxSpeed) {
for (int i = 0; i < getTaskList().size(); i++) {
AbsThreadTask task = getTaskList().valueAt(i);
if (task != null && mStartThreadNum > 0) {
task.setMaxSpeed(maxSpeed / mStartThreadNum);
}
}
}
@Override public void onDestroy() {
super.onDestroy();
EventMsgUtil.getDefault().unRegister(this);
}
@Override protected void onPostPre() {
super.onPostPre();
mTotalThreadNum = mRecord.threadNum;
}
@Override protected IThreadState getStateManager(Looper looper) {
mStateManager = new ThreadStateManager(looper, mRecord, mListener);
mStateHandler = new Handler(looper, mStateManager);
return mStateManager;
}
@Override protected void handleTask() {
if (mTaskWrapper.isSupportBP()) {
handleBreakpoint();
} else {
handleNoSupportBP();
}
}
/**
* 启动断点任务时创建单线程任务
*
* @param record 线程记录
* @param startNum 启动的线程数
*/
private AbsThreadTask createSingThreadTask(ThreadRecord record, int startNum) {
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>();
config.url = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.tempFile =
mRecord.isBlock ? new File(
String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), record.threadId))
: mTempFile;
config.isBlock = mRecord.isBlock;
config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
config.startThreadNum = startNum;
config.taskWrapper = mTaskWrapper;
config.record = record;
config.stateHandler = mStateHandler;
return selectThreadTask(config);
}
private void handleBreakpoint() {
long fileLength = mEntity.getFileSize();
long blockSize = fileLength / mTotalThreadNum;
long currentProgress = 0;
mRecord.fileLength = fileLength;
if (mTaskWrapper.isNewTask() && !handleNewTask()) {
return;
}
for (ThreadRecord tr : mRecord.threadRecords) {
if (!tr.isComplete) {
mStartThreadNum++;
}
}
for (int i = 0; i < mTotalThreadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr = mRecord.threadRecords.get(i);
if (tr.isComplete) {//该线程已经完成
currentProgress += endL - startL;
ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mTaskWrapper.getEntity().getFileName(), i));
mStateHandler.obtainMessage(IThreadState.STATE_COMPLETE).sendToTarget();
if (mStateManager.isComplete()) {
mRecord.deleteData();
mListener.onComplete();
return;
}
continue;
}
//如果有记录,则恢复任务
long r = tr.startLocation;
//记录的位置需要在线程区间中
if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) {
currentProgress += r - startL;
}
ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i));
AbsThreadTask task = createSingThreadTask(tr, mStartThreadNum);
if (task == null) return;
getTaskList().put(tr.threadId, task);
}
if (currentProgress != 0 && currentProgress != mEntity.getCurrentProgress()) {
ALog.d(TAG, String.format("进度修正,当前进度:%s", currentProgress));
mEntity.setCurrentProgress(currentProgress);
}
mStateHandler.obtainMessage(IThreadState.STATE_UPDATE_PROGRESS, currentProgress)
.sendToTarget();
startThreadTask();
}
/**
* 启动单线程任务
*/
private void startThreadTask() {
if (isBreak()) {
return;
}
if (mStateManager.getCurrentProgress() > 0) {
mListener.onResume(mStateManager.getCurrentProgress());
} else {
mListener.onStart(mStateManager.getCurrentProgress());
}
for (int i = 0; i < getTaskList().size(); i++) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), getTaskList().valueAt(i));
}
}
/**
* 处理不支持断点的任务
*/
private void handleNoSupportBP() {
if (mListener instanceof BaseDListener) {
((BaseDListener) mListener).supportBreakpoint(false);
}
mStartThreadNum = 1;
AbsThreadTask task = createSingThreadTask(mRecord.threadRecords.get(0), 1);
if (task == null) return;
getTaskList().put(0, task);
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task);
mListener.onStart(0);
}
}

@ -16,6 +16,12 @@
package com.arialyy.aria.core.common; package com.arialyy.aria.core.common;
import com.arialyy.annotations.TaskEnum; import com.arialyy.annotations.TaskEnum;
import com.arialyy.aria.core.download.DownloadGroupTaskListener;
import com.arialyy.aria.core.download.DownloadTaskListener;
import com.arialyy.aria.core.scheduler.M3U8PeerTaskListener;
import com.arialyy.aria.core.scheduler.SubTaskListener;
import com.arialyy.aria.core.scheduler.TaskInternalListenerInterface;
import com.arialyy.aria.core.upload.UploadTaskListener;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -69,61 +75,86 @@ public class ProxyHelper {
* @return {@link #PROXY_TYPE_DOWNLOAD}如果没有实体对象则返回空的list * @return {@link #PROXY_TYPE_DOWNLOAD}如果没有实体对象则返回空的list
*/ */
public Set<Integer> checkProxyType(Class clazz) { public Set<Integer> checkProxyType(Class clazz) {
final String className = clazz.getName();
Set<Integer> result = mProxyCache.get(clazz.getName()); Set<Integer> result = mProxyCache.get(clazz.getName());
if (result != null) { if (result != null) {
return result; return result;
} }
result = new HashSet<>(); result = checkProxyTypeByInterface(clazz);
try { if (result != null && !result.isEmpty()) {
if (getClass().getClassLoader() mProxyCache.put(clazz.getName(), result);
.loadClass(className.concat(TaskEnum.DOWNLOAD_GROUP.proxySuffix)) return result;
!= null) {
result.add(PROXY_TYPE_DOWNLOAD_GROUP);
} }
} catch (ClassNotFoundException e) { result = checkProxyTypeByProxyClass(clazz);
//e.printStackTrace();
if (!result.isEmpty()) {
mProxyCache.put(clazz.getName(), result);
} }
try { return result;
if (getClass().getClassLoader().loadClass(className.concat(TaskEnum.DOWNLOAD.proxySuffix))
!= null) {
result.add(PROXY_TYPE_DOWNLOAD);
} }
} catch (ClassNotFoundException e) {
//e.printStackTrace(); private Set<Integer> checkProxyTypeByProxyClass(Class clazz) {
final String className = clazz.getName();
Set<Integer> result = new HashSet<>();
if (checkProxyExist(className, TaskEnum.DOWNLOAD_GROUP.proxySuffix)) {
result.add(PROXY_TYPE_DOWNLOAD_GROUP);
}
if (checkProxyExist(className, TaskEnum.DOWNLOAD.proxySuffix)) {
result.add(PROXY_TYPE_DOWNLOAD);
} }
try { if (checkProxyExist(className, TaskEnum.UPLOAD.proxySuffix)) {
if (getClass().getClassLoader().loadClass(className.concat(TaskEnum.UPLOAD.proxySuffix))
!= null) {
result.add(PROXY_TYPE_UPLOAD); result.add(PROXY_TYPE_UPLOAD);
} }
} catch (ClassNotFoundException e) {
//e.printStackTrace(); if (checkProxyExist(className, TaskEnum.M3U8_PEER.proxySuffix)) {
result.add(PROXY_TYPE_M3U8_PEER);
} }
try { if (checkProxyExist(className, TaskEnum.DOWNLOAD_GROUP_SUB.proxySuffix)) {
if (getClass().getClassLoader().loadClass(className.concat(TaskEnum.M3U8_PEER.proxySuffix)) result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB);
!= null) { }
return result;
}
private Set<Integer> checkProxyTypeByInterface(Class clazz) {
if (!TaskInternalListenerInterface.class.isAssignableFrom(clazz)) {
return null;
}
Set<Integer> result = new HashSet<>();
if (DownloadGroupTaskListener.class.isAssignableFrom(clazz)) {
result.add(PROXY_TYPE_DOWNLOAD_GROUP);
}
if (DownloadTaskListener.class.isAssignableFrom(clazz)) {
result.add(PROXY_TYPE_DOWNLOAD);
}
if (UploadTaskListener.class.isAssignableFrom(clazz)) {
result.add(PROXY_TYPE_UPLOAD);
}
if (M3U8PeerTaskListener.class.isAssignableFrom(clazz)) {
result.add(PROXY_TYPE_M3U8_PEER); result.add(PROXY_TYPE_M3U8_PEER);
} }
} catch (ClassNotFoundException e) {
//e.printStackTrace(); if (SubTaskListener.class.isAssignableFrom(clazz)) {
result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB);
} }
return result;
}
private boolean checkProxyExist(String className, String proxySuffix) {
String clsName = className.concat(proxySuffix);
try { try {
if (getClass().getClassLoader() if (getClass().getClassLoader().loadClass(clsName) != null) {
.loadClass(className.concat(TaskEnum.DOWNLOAD_GROUP_SUB.proxySuffix)) return true;
!= null) { }
result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB); if (Class.forName(clsName) != null) {
return true;
} }
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
//e.printStackTrace(); //e.printStackTrace();
} }
return false;
if (!result.isEmpty()) {
mProxyCache.put(clazz.getName(), result);
}
return result;
} }
} }

@ -1,457 +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.common;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.m3u8.BaseM3U8Loader;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DbDataHelper;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* 处理任务记录分配线程区间
*/
public class RecordHandler {
private final String TAG = "RecordHandler";
public static final int TYPE_DOWNLOAD = 1;
public static final int TYPE_UPLOAD = 2;
public static final int TYPE_M3U8_VOD = 3;
public static final int TYPE_M3U8_LIVE = 4;
private static final String STATE = "_state_";
private static final String RECORD = "_record_";
/**
* 小于1m的文件不启用多线程
*/
private static final long SUB_LEN = 1024 * 1024;
/**
* 分块文件路径
*/
public static final String SUB_PATH = "%s.%s.part";
@Deprecated private File mConfigFile;
private TaskRecord mTaskRecord;
private AbsTaskWrapper mTaskWrapper;
private AbsNormalEntity mEntity;
RecordHandler(AbsTaskWrapper wrapper) {
mTaskWrapper = wrapper;
mEntity = (AbsNormalEntity) mTaskWrapper.getEntity();
}
/**
* 获取任务记录如果任务记录存在检查任务记录
* 检查记录 对于分块任务 子分块不存在或被删除子线程将重新下载
* 对于普通任务 预下载文件不存在则任务任务呗删除
* 如果任务记录不存在或线程记录不存在初始化记录
*
* @return 任务记录
*/
TaskRecord getRecord() {
mConfigFile = new File(CommonUtil.getFileConfigPath(false, mEntity.getFileName()));
if (mConfigFile.exists()) {
convertDb();
} else {
mTaskRecord = DbDataHelper.getTaskRecord(getFilePath());
if (mTaskRecord == null) {
initRecord(true);
} else {
if (mTaskRecord.threadRecords == null || mTaskRecord.threadRecords.isEmpty()) {
initRecord(false);
}
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
handleM3U8Record();
} else if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
ALog.i(TAG, "直播下载不处理历史记录");
} else {
if (mTaskRecord.isBlock) {
handleBlockRecord();
} else if (!mTaskWrapper.isSupportBP()) {
handleNoSupportBPRecord();
} else if (!mTaskRecord.isBlock && mTaskRecord.threadNum > 1) {
handleNoBlockMultiThreadRecord();
} else {
handleSingleThreadRecord();
}
}
}
}
saveRecord();
return mTaskRecord;
}
/**
* 处理m3u8记录
* 1如果分片文件存在并且分片文件的记录没有完成则需要删除该分片文件
* 2如果记录显示已完成但是分片文件不存在则重新开始该分片
* 3如果记录显示已完成并且文件存在记录当前任务进度
*/
private void handleM3U8Record() {
DTaskWrapper wrapper = (DTaskWrapper) mTaskWrapper;
String cacheDir = wrapper.asM3U8().getCacheDir();
long currentProgress = 0;
int completeNum = 0;
for (ThreadRecord record : mTaskRecord.threadRecords) {
File temp = new File(BaseM3U8Loader.getTsFilePath(cacheDir, record.threadId));
if (!record.isComplete) {
if (temp.exists()) {
temp.delete();
}
record.startLocation = 0;
//ALog.d(TAG, String.format("分片【%s】未完成,将重新下载该分片", record.threadId));
} else {
if (!temp.exists()) {
record.startLocation = 0;
record.isComplete = false;
ALog.w(TAG, String.format("分片【%s】不存在,将重新下载该分片", record.threadId));
} else {
completeNum++;
currentProgress += temp.length();
}
}
}
wrapper.asM3U8().setCompleteNum(completeNum);
wrapper.getEntity().setCurrentProgress(currentProgress);
mTaskRecord.bandWidth = wrapper.asM3U8().getBandWidth();
}
/**
* 处理不支持断点的记录
*/
private void handleNoSupportBPRecord() {
ThreadRecord tr = mTaskRecord.threadRecords.get(0);
tr.startLocation = 0;
tr.endLocation = mEntity.getFileSize();
tr.taskKey = mTaskRecord.filePath;
tr.blockLen = tr.endLocation;
tr.isComplete = false;
}
/**
* 处理为不分块的多线程任务
*/
private void handleNoBlockMultiThreadRecord() {
File file = new File(mTaskRecord.filePath);
if (!file.exists()) {
ALog.w(TAG, String.format("文件【%s】不存在,重新分配线程区间", mTaskRecord.filePath));
DbEntity.deleteData(ThreadRecord.class, "taskKey=?", mTaskRecord.filePath);
initRecord(false);
}
}
/**
* 处理单线程的任务的记录
*/
private void handleSingleThreadRecord() {
File file = new File(mTaskRecord.filePath);
ThreadRecord tr = mTaskRecord.threadRecords.get(0);
if (!file.exists()) {
ALog.w(TAG, String.format("文件【%s】不存在,任务将重新开始", file.getPath()));
tr.startLocation = 0;
tr.isComplete = false;
tr.endLocation = mEntity.getFileSize();
} else if (mTaskRecord.isOpenDynamicFile) {
if (file.length() > mEntity.getFileSize()) {
ALog.i(TAG, String.format("文件【%s】错误,任务重新开始", file.getPath()));
file.delete();
tr.startLocation = 0;
tr.isComplete = false;
tr.endLocation = mEntity.getFileSize();
} else if (file.length() == mEntity.getFileSize()) {
tr.isComplete = true;
} else {
if (file.length() != tr.startLocation) {
ALog.i(TAG, String.format("修正【%s】的进度记录为:%s", file.getPath(), file.length()));
tr.startLocation = file.length();
tr.isComplete = false;
}
}
}
mTaskWrapper.setNewTask(false);
}
/**
* 处理分块任务的记录分块文件blockFileLen长度必须需要小于等于线程区间threadRectLen的长度
*/
private void handleBlockRecord() {
// 默认线程分块长度
long normalRectLen = mEntity.getFileSize() / mTaskRecord.threadRecords.size();
for (ThreadRecord tr : mTaskRecord.threadRecords) {
long threadRect = tr.blockLen;
File temp = new File(String.format(SUB_PATH, mTaskRecord.filePath, tr.threadId));
if (!temp.exists()) {
ALog.i(TAG, String.format("分块文件【%s】不存在,该分块将重新开始", temp.getPath()));
tr.isComplete = false;
tr.startLocation = tr.threadId * normalRectLen;
} else {
if (!tr.isComplete) {
ALog.i(TAG, String.format(
"startLocation = %s; endLocation = %s; block = %s; tempLen = %s; threadId = %s",
tr.startLocation, tr.endLocation, threadRect, temp.length(), tr.threadId));
long blockFileLen = temp.length(); // 磁盘中的分块文件长度
/*
* 检查磁盘中的分块文件
*/
if (blockFileLen > threadRect) {
ALog.i(TAG, String.format("分块【%s】错误,分块长度【%s】 > 线程区间长度【%s】,将重新开始该分块",
tr.threadId, blockFileLen, threadRect));
temp.delete();
tr.startLocation = tr.threadId * threadRect;
continue;
}
long realLocation =
tr.threadId * normalRectLen + blockFileLen; //正常情况下,该线程的startLocation的位置
/*
* 检查记录文件
*/
if (blockFileLen == threadRect) {
ALog.i(TAG, String.format("分块【%s】已完成,更新记录", temp.getPath()));
tr.startLocation = blockFileLen;
tr.isComplete = true;
} else if (tr.startLocation != realLocation) { // 处理记录小于分块文件长度的情况
ALog.i(TAG, String.format("修正分块【%s】的进度记录为:%s", temp.getPath(), realLocation));
tr.startLocation = realLocation;
}
} else {
ALog.i(TAG, String.format("分块【%s】已完成", temp.getPath()));
}
}
}
mTaskWrapper.setNewTask(false);
}
/**
* convertDb 是兼容性代码 从3.4.1开始线程配置信息将存储在数据库中 将配置文件的内容复制到数据库中并将配置文件删除
*/
private void convertDb() {
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?",
getFilePath());
if (records == null || records.size() == 0) {
Properties pro = FileUtil.loadConfig(mConfigFile);
if (pro.isEmpty()) {
ALog.d(TAG, "老版本的线程记录为空,任务为新任务");
initRecord(true);
return;
}
Set<Object> keys = pro.keySet();
// 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录...
// 第一步应该是record 和 state去重取正确的线程数
Set<Integer> set = new HashSet<>();
for (Object key : keys) {
String str = String.valueOf(key);
int i = Integer.parseInt(str.substring(str.length() - 1));
set.add(i);
}
int threadNum = set.size();
if (threadNum == 0) {
ALog.d(TAG, "线程数为空,任务为新任务");
initRecord(true);
return;
}
mTaskWrapper.setNewTask(false);
mTaskRecord = createTaskRecord(threadNum);
mTaskRecord.isOpenDynamicFile = false;
mTaskRecord.isBlock = false;
File tempFile = new File(getFilePath());
for (int i = 0; i < threadNum; i++) {
ThreadRecord tRecord = new ThreadRecord();
tRecord.taskKey = mTaskRecord.filePath;
Object state = pro.getProperty(tempFile.getName() + STATE + i);
Object record = pro.getProperty(tempFile.getName() + RECORD + i);
if (state != null && Integer.parseInt(String.valueOf(state)) == 1) {
tRecord.isComplete = true;
continue;
}
if (record != null) {
long temp = Long.parseLong(String.valueOf(record));
tRecord.startLocation = temp > 0 ? temp : 0;
} else {
tRecord.startLocation = 0;
}
mTaskRecord.threadRecords.add(tRecord);
}
mConfigFile.delete();
}
}
/**
* 初始化任务记录分配线程区间
*
* @param newRecord {@code true} 需要创建新{@link TaskRecord}
*/
private void initRecord(boolean newRecord) {
if (newRecord) {
mTaskRecord = createTaskRecord(getNewTaskThreadNum());
}
mTaskWrapper.setNewTask(true);
int requestType = mTaskWrapper.getRequestType();
if (requestType == ITaskWrapper.M3U8_LIVE) {
return;
}
long blockSize = mEntity.getFileSize() / mTaskRecord.threadNum;
// 处理线程区间记录
for (int i = 0; i < mTaskRecord.threadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr;
tr = new ThreadRecord();
tr.taskKey = mTaskRecord.filePath;
tr.threadId = i;
tr.startLocation = startL;
tr.isComplete = false;
if (requestType == ITaskWrapper.M3U8_VOD) {
tr.startLocation = 0;
tr.threadType = TaskRecord.TYPE_M3U8_VOD;
tr.tsUrl = ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().get(i);
} else {
tr.threadType = TaskRecord.TYPE_HTTP_FTP;
//最后一个线程的结束位置即为文件的总长度
if (i == (mTaskRecord.threadNum - 1)) {
endL = mEntity.getFileSize();
}
tr.endLocation = endL;
tr.blockLen = RecordUtil.getBlockLen(mEntity.getFileSize(), i, mTaskRecord.threadNum);
}
mTaskRecord.threadRecords.add(tr);
}
}
/**
* 创建任务记录
*
* @param threadNum 线程总数
*/
private TaskRecord createTaskRecord(int threadNum) {
TaskRecord record = new TaskRecord();
record.fileName = mEntity.getFileName();
record.filePath = getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
int requestType = mTaskWrapper.getRequestType();
if (requestType == ITaskWrapper.M3U8_VOD) {
record.taskType = TaskRecord.TYPE_M3U8_VOD;
record.isOpenDynamicFile = true;
record.bandWidth = ((DTaskWrapper)mTaskWrapper).asM3U8().getBandWidth();
} else if (requestType == ITaskWrapper.M3U8_LIVE) {
record.taskType = TaskRecord.TYPE_M3U8_LIVE;
record.isOpenDynamicFile = true;
record.bandWidth = ((DTaskWrapper)mTaskWrapper).asM3U8().getBandWidth();
} else {
if (getRecordType() == TYPE_DOWNLOAD) {
record.isBlock = threadNum > 1 && Configuration.getInstance().downloadCfg.isUseBlock();
// 线程数为1,或者使用了分块,则认为是使用动态长度文件
record.isOpenDynamicFile = threadNum == 1 || record.isBlock;
} else {
record.isBlock = false;
}
record.taskType = TaskRecord.TYPE_HTTP_FTP;
record.isGroupRecord = mEntity.isGroupChild();
if (record.isGroupRecord) {
if (mEntity instanceof DownloadEntity) {
record.dGroupHash = ((DownloadEntity) mEntity).getGroupHash();
}
}
}
return record;
}
/**
* 保存任务记录
*/
private void saveRecord() {
mTaskRecord.threadNum = mTaskRecord.threadRecords.size();
mTaskRecord.save();
if (mTaskRecord.threadRecords != null && !mTaskRecord.threadRecords.isEmpty()) {
DbEntity.saveAll(mTaskRecord.threadRecords);
}
ALog.d(TAG, String.format("保存记录,线程记录数:%s", mTaskRecord.threadRecords.size()));
}
/**
* 获取记录类型
*
* @return {@link #TYPE_DOWNLOAD}{@link #TYPE_UPLOAD}
*/
private int getRecordType() {
if (mEntity instanceof DownloadEntity) {
return TYPE_DOWNLOAD;
} else {
return TYPE_UPLOAD;
}
}
/**
* 获取任务路径
*
* @return 任务文件路径
*/
private String getFilePath() {
if (mEntity instanceof DownloadEntity) {
return ((DownloadEntity) mTaskWrapper.getEntity()).getFilePath();
} else {
return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath();
}
}
/**
* 小于1m的文件或是任务组的子任务线程数强制为1
* 不支持断点或chunked模式的线程数都为线程数强制为1
*/
private int getNewTaskThreadNum() {
if (getRecordType() == TYPE_DOWNLOAD) {
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
return ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().size();
}
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
return 1;
}
if (!mTaskWrapper.isSupportBP() || mTaskWrapper.asHttp().isChunked()) {
return 1;
}
int threadNum = Configuration.getInstance().downloadCfg.getThreadNum();
return mEntity.getFileSize() <= SUB_LEN
|| mEntity.isGroupChild()
|| threadNum == 1
? 1
: threadNum;
} else {
return 1;
}
}
}

@ -0,0 +1,135 @@
/*
* 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;
import android.text.TextUtils;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.IdEntity;
import com.arialyy.aria.core.ProtocolType;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.ComponentUtil;
/**
* Created by laoyuyu on 2018/3/9.
*/
public class SFtpOption extends BaseOption {
private String charSet, userName, password;
private boolean isNeedLogin = false;
private FtpUrlEntity urlEntity;
private String protocol;
private IdEntity idEntity = new IdEntity();
public SFtpOption() {
super();
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_SFTP);
}
public SFtpOption charSet(String charSet) {
if (TextUtils.isEmpty(charSet)) {
throw new NullPointerException("字符编码为空");
}
this.charSet = charSet;
return this;
}
public SFtpOption login(String userName, String password) {
if (TextUtils.isEmpty(userName)) {
ALog.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
ALog.e(TAG, "密码不能为null");
return this;
}
this.userName = userName;
this.password = password;
isNeedLogin = true;
return this;
}
/**
* 设置协议类型
*
* @param protocol {@link ProtocolType}
*/
public SFtpOption setProtocol(String protocol) {
if (TextUtils.isEmpty(protocol)) {
ALog.e(TAG, "设置协议失败,协议信息为空");
return this;
}
this.protocol = protocol;
return this;
}
/**
* 设置私钥证书路径
*
* @param prvKey 证书路径
*/
public SFtpOption setPrvKey(String prvKey) {
if (TextUtils.isEmpty(prvKey)) {
ALog.e(TAG, "设置私钥证书失败,证书内容为空");
return this;
}
idEntity.prvKey = prvKey;
return this;
}
/**
* 设置私钥密码
*
* @param prvKeyPass 私钥密码
*/
public SFtpOption setPrvKeyPass(String prvKeyPass) {
if (TextUtils.isEmpty(prvKeyPass)) {
ALog.e(TAG, "设置证书密码失败,证书密码为空");
return this;
}
idEntity.prvPass = prvKeyPass;
return this;
}
/**
* 设置公钥证书
*
* @param pubKey 公钥证书内容
*/
public SFtpOption setPubKey(String pubKey) {
if (TextUtils.isEmpty(pubKey)) {
ALog.e(TAG, "设置公钥失败,证书内容为空");
return this;
}
idEntity.pubKey = pubKey;
return this;
}
public SFtpOption setKnowHostPath(String knowHostPath) {
if (TextUtils.isEmpty(knowHostPath)) {
ALog.e(TAG, "knowhost 文件路径为空");
return this;
}
idEntity.knowHost = knowHostPath;
return this;
}
public void setUrlEntity(FtpUrlEntity urlEntity) {
this.urlEntity = urlEntity;
urlEntity.needLogin = isNeedLogin;
urlEntity.user = userName;
urlEntity.password = password;
urlEntity.idEntity = idEntity;
}
}

@ -1,219 +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.common;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 线程任务管理器用于处理多线程下载时任务的状态回调
*/
public class ThreadStateManager implements IThreadState {
private final String TAG = "ThreadTaskStateManager";
/**
* 任务状态回调
*/
private IEventListener mListener;
private int mThreadNum; // 启动的线程总数
private int mCancelNum = 0; // 已经取消的线程的数
private int mStopNum = 0; // 已经停止的线程数
private int mFailNum = 0; // 失败的线程数
private int mCompleteNum = 0; // 完成的线程数
private long mProgress; //当前总进度
private TaskRecord mTaskRecord; // 任务记录
private Looper mLooper;
/**
* @param taskRecord 任务记录
* @param listener 任务事件
*/
ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) {
mLooper = looper;
mTaskRecord = taskRecord;
mThreadNum = mTaskRecord.threadNum;
mListener = listener;
}
@Override public boolean handleMessage(Message msg) {
switch (msg.what) {
case STATE_STOP:
mStopNum++;
if (isStop()) {
quitLooper();
}
break;
case STATE_CANCEL:
mCancelNum++;
if (isCancel()) {
quitLooper();
}
break;
case STATE_FAIL:
mFailNum++;
if (isFail()) {
Bundle b = msg.getData();
mListener.onFail(b.getBoolean(KEY_RETRY, true),
(BaseException) b.getSerializable(KEY_ERROR_INFO));
quitLooper();
}
break;
case STATE_COMPLETE:
mCompleteNum++;
if (isComplete()) {
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
if (mTaskRecord.isBlock) {
if (mergeFile()) {
mListener.onComplete();
} else {
mListener.onFail(false, null);
}
} else {
mListener.onComplete();
}
quitLooper();
}
break;
case STATE_RUNNING:
if (msg.obj instanceof Long) {
mProgress += (long) msg.obj;
}
break;
case STATE_UPDATE_PROGRESS:
if (msg.obj == null) {
mProgress = updateBlockProgress();
} else if (msg.obj instanceof Long) {
mProgress = (long) msg.obj;
}
break;
}
return false;
}
/**
* 退出looper循环
*/
private void quitLooper() {
mLooper.quit();
}
/**
* 获取当前任务下载进度
*
* @return 当前任务下载进度
*/
@Override
public long getCurrentProgress() {
return mProgress;
}
/**
* 所有子线程是否都已经停止
*/
public boolean isStop() {
//ALog.d(TAG,
// String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mStopNum == mThreadNum || mStopNum + mCompleteNum == mThreadNum;
}
/**
* 所有子线程是否都已经失败
*/
@Override
public boolean isFail() {
//ALog.d(TAG,
// String.format("isFail; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum != mThreadNum
&& (mFailNum == mThreadNum || mFailNum + mCompleteNum == mThreadNum);
}
/**
* 所有子线程是否都已经完成
*/
@Override
public boolean isComplete() {
//ALog.d(TAG,
// String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum == mThreadNum;
}
/**
* 所有子线程是否都已经取消
*/
public boolean isCancel() {
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCancelNum == mThreadNum;
}
/**
* 更新分块任务s的真实进度
*/
private long updateBlockProgress() {
long size = 0;
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) {
File temp = new File(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
private boolean mergeFile() {
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
}

@ -15,10 +15,10 @@
*/ */
package com.arialyy.aria.core.common.controller; package com.arialyy.aria.core.common.controller;
import com.arialyy.aria.core.command.CmdHelper;
import com.arialyy.aria.core.command.NormalCmdFactory; import com.arialyy.aria.core.command.NormalCmdFactory;
import com.arialyy.aria.core.event.EventMsgUtil; import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.CommonUtil;
/** /**
* 创建任务时使用的控制器 * 创建任务时使用的控制器
@ -30,14 +30,15 @@ public final class BuilderController extends FeatureController implements IStart
} }
/** /**
* 添加任务 * 添加任务只添加任务不进行下载
* *
* @return 正常添加返回任务id否则返回-1 * @return 正常添加返回任务id否则返回-1
*/ */
public long add() { public long add() {
setAction(ACTION_ADD);
if (checkConfig()) { if (checkConfig()) {
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CREATE, .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CREATE,
checkTaskType())); checkTaskType()));
return getEntity().getId(); return getEntity().getId();
} }
@ -50,20 +51,21 @@ public final class BuilderController extends FeatureController implements IStart
* @return 正常启动返回任务id否则返回-1 * @return 正常启动返回任务id否则返回-1
*/ */
public long create() { public long create() {
setAction(ACTION_CREATE);
if (checkConfig()) { if (checkConfig()) {
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START,
checkTaskType())); checkTaskType()));
return getEntity().getId(); return getEntity().getId();
} }
return -1; return -1;
} }
@Override public long setHighestPriority() { @Override public long setHighestPriority() {
setAction(ACTION_PRIORITY);
if (checkConfig()) { if (checkConfig()) {
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_HIGHEST_PRIORITY, .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_HIGHEST_PRIORITY,
checkTaskType())); checkTaskType()));
return getEntity().getId(); return getEntity().getId();
} }

@ -19,21 +19,21 @@ import android.Manifest;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.download.CheckDEntityUtil; import com.arialyy.aria.core.download.CheckDEntityUtil;
import com.arialyy.aria.core.download.CheckDGEntityUtil; import com.arialyy.aria.core.download.CheckDGEntityUtil;
import com.arialyy.aria.core.download.CheckFtpDirEntityUtil; import com.arialyy.aria.core.download.CheckFtpDirEntityUtil;
import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DTaskWrapper; 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.ICheckEntityUtil; import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.core.inf.ITask; import com.arialyy.aria.core.listener.ISchedulers;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.core.scheduler.TaskSchedulers; import com.arialyy.aria.core.scheduler.TaskSchedulers;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.core.upload.CheckUEntityUtil; import com.arialyy.aria.core.upload.CheckUEntityUtil;
import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -43,9 +43,25 @@ import java.lang.reflect.InvocationTargetException;
* 功能控制器 * 功能控制器
*/ */
public abstract class FeatureController { public abstract class FeatureController {
private static final int ACTION_DEF = 0;
public static final int ACTION_CREATE = 1;
public static final int ACTION_RESUME = 2;
public static final int ACTION_STOP = 3;
public static final int ACTION_CANCEL = 4;
public static final int ACTION_ADD = 5;
public static final int ACTION_PRIORITY = 6;
public static final int ACTION_RETRY = 7;
public static final int ACTION_RESTART = 8;
public static final int ACTION_SAVE = 9;
private final String TAG; private final String TAG;
private AbsTaskWrapper mTaskWrapper; private AbsTaskWrapper mTaskWrapper;
/**
* 是否忽略权限检查 true 忽略权限检查
*/
private boolean ignoreCheckPermissions = false;
private int action = ACTION_DEF;
FeatureController(AbsTaskWrapper wrapper) { FeatureController(AbsTaskWrapper wrapper) {
mTaskWrapper = wrapper; mTaskWrapper = wrapper;
@ -87,6 +103,24 @@ public abstract class FeatureController {
return null; return null;
} }
void setAction(int action) {
this.action = action;
}
/**
* 是否忽略权限检查
*/
public void ignoreCheckPermissions() {
this.ignoreCheckPermissions = true;
}
/**
* 强制执行任务不管文件路径是否被占用
*/
public void ignoreFilePathOccupy() {
mTaskWrapper.setIgnoreFilePathOccupy(true);
}
protected AbsTaskWrapper getTaskWrapper() { protected AbsTaskWrapper getTaskWrapper() {
return mTaskWrapper; return mTaskWrapper;
} }
@ -111,7 +145,7 @@ public abstract class FeatureController {
* 如果检查实体失败将错误回调 * 如果检查实体失败将错误回调
*/ */
boolean checkConfig() { boolean checkConfig() {
if (!checkPermission()) { if (!ignoreCheckPermissions && !checkPermission()) {
return false; return false;
} }
boolean b = checkEntity(); boolean b = checkEntity();
@ -134,21 +168,21 @@ public abstract class FeatureController {
*/ */
private boolean checkPermission() { private boolean checkPermission() {
if (AriaManager.getInstance() if (AriaConfig.getInstance()
.getAPP() .getAPP()
.checkCallingOrSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) .checkCallingOrSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
ALog.e(TAG, "启动失败,缺少权限:Manifest.permission.WRITE_EXTERNAL_STORAGE"); ALog.e(TAG, "启动失败,缺少权限:Manifest.permission.WRITE_EXTERNAL_STORAGE");
return false; return false;
} }
if (AriaManager.getInstance() if (AriaConfig.getInstance()
.getAPP() .getAPP()
.checkCallingOrSelfPermission(Manifest.permission.INTERNET) .checkCallingOrSelfPermission(Manifest.permission.INTERNET)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
ALog.e(TAG, "启动失败,缺少权限:Manifest.permission.INTERNET"); ALog.e(TAG, "启动失败,缺少权限:Manifest.permission.INTERNET");
return false; return false;
} }
if (AriaManager.getInstance() if (AriaConfig.getInstance()
.getAPP() .getAPP()
.checkCallingOrSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) .checkCallingOrSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
@ -162,15 +196,15 @@ public abstract class FeatureController {
private boolean checkEntity() { private boolean checkEntity() {
ICheckEntityUtil checkUtil = null; ICheckEntityUtil checkUtil = null;
if (mTaskWrapper instanceof DTaskWrapper) { if (mTaskWrapper instanceof DTaskWrapper) {
checkUtil = CheckDEntityUtil.newInstance((DTaskWrapper) mTaskWrapper); checkUtil = CheckDEntityUtil.newInstance((DTaskWrapper) mTaskWrapper, action);
} else if (mTaskWrapper instanceof DGTaskWrapper) { } else if (mTaskWrapper instanceof DGTaskWrapper) {
if (mTaskWrapper.getRequestType() == ITaskWrapper.D_FTP_DIR) { if (mTaskWrapper.getRequestType() == ITaskWrapper.D_FTP_DIR) {
checkUtil = CheckFtpDirEntityUtil.newInstance((DGTaskWrapper) mTaskWrapper); checkUtil = CheckFtpDirEntityUtil.newInstance((DGTaskWrapper) mTaskWrapper, action);
} else if (mTaskWrapper.getRequestType() == ITaskWrapper.DG_HTTP) { } else if (mTaskWrapper.getRequestType() == ITaskWrapper.DG_HTTP) {
checkUtil = CheckDGEntityUtil.newInstance((DGTaskWrapper) mTaskWrapper); checkUtil = CheckDGEntityUtil.newInstance((DGTaskWrapper) mTaskWrapper, action);
} }
} else if (mTaskWrapper instanceof UTaskWrapper) { } else if (mTaskWrapper instanceof UTaskWrapper) {
checkUtil = CheckUEntityUtil.newInstance((UTaskWrapper) mTaskWrapper); checkUtil = CheckUEntityUtil.newInstance((UTaskWrapper) mTaskWrapper, action);
} }
return checkUtil != null && checkUtil.checkEntity(); return checkUtil != null && checkUtil.checkEntity();
} }

@ -30,6 +30,14 @@ public interface INormalFeature {
*/ */
void resume(); void resume();
/**
* 正常来说当执行队列满时调用恢复任务接口只能将任务放到缓存队列中
* 如果希望调用恢复接口马上进入执行队列需要使用该方法
*
* @param newStart true 立即将任务恢复到执行队列中
*/
void resume(boolean newStart);
/** /**
* 删除任务 * 删除任务
*/ */
@ -51,7 +59,7 @@ public interface INormalFeature {
/** /**
* 重新下载 * 重新下载
*/ */
void reStart(); long reStart();
/** /**
* 保存数据 * 保存数据

@ -16,11 +16,12 @@
package com.arialyy.aria.core.common.controller; package com.arialyy.aria.core.common.controller;
import com.arialyy.aria.core.command.CancelCmd; import com.arialyy.aria.core.command.CancelCmd;
import com.arialyy.aria.core.command.CmdHelper;
import com.arialyy.aria.core.command.NormalCmdFactory; import com.arialyy.aria.core.command.NormalCmdFactory;
import com.arialyy.aria.core.command.StartCmd;
import com.arialyy.aria.core.event.EventMsgUtil; import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/** /**
* 启动控制器 * 启动控制器
@ -37,9 +38,10 @@ public final class NormalController extends FeatureController implements INormal
*/ */
@Override @Override
public void stop() { public void stop() {
setAction(ACTION_STOP);
if (checkConfig()) { if (checkConfig()) {
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP,
checkTaskType())); checkTaskType()));
} }
} }
@ -49,10 +51,24 @@ public final class NormalController extends FeatureController implements INormal
*/ */
@Override @Override
public void resume() { public void resume() {
resume(false);
}
/**
* 正常来说当执行队列满时调用恢复任务接口只能将任务放到缓存队列中
* 如果希望调用恢复接口马上进入执行队列需要使用该方法
*
* @param newStart true 立即将任务恢复到执行队列中
*/
@Override public void resume(boolean newStart) {
setAction(ACTION_RESUME);
if (checkConfig()) { if (checkConfig()) {
StartCmd cmd =
(StartCmd) CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START,
checkTaskType());
cmd.setNowStart(newStart);
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, .post(cmd);
checkTaskType()));
} }
} }
@ -61,11 +77,7 @@ public final class NormalController extends FeatureController implements INormal
*/ */
@Override @Override
public void cancel() { public void cancel() {
if (checkConfig()) { cancel(false);
EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CANCEL,
checkTaskType()));
}
} }
/** /**
@ -73,13 +85,14 @@ public final class NormalController extends FeatureController implements INormal
*/ */
@Override @Override
public void reTry() { public void reTry() {
setAction(ACTION_RETRY);
if (checkConfig()) { if (checkConfig()) {
int taskType = checkTaskType(); int taskType = checkTaskType();
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, taskType)); .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, taskType));
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post( .post(
CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, taskType)); CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, taskType));
} }
} }
@ -91,9 +104,10 @@ public final class NormalController extends FeatureController implements INormal
*/ */
@Override @Override
public void cancel(boolean removeFile) { public void cancel(boolean removeFile) {
setAction(ACTION_CANCEL);
if (checkConfig()) { if (checkConfig()) {
CancelCmd cancelCmd = CancelCmd cancelCmd =
(CancelCmd) CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CANCEL, (CancelCmd) CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CANCEL,
checkTaskType()); checkTaskType());
cancelCmd.removeFile = removeFile; cancelCmd.removeFile = removeFile;
EventMsgUtil.getDefault().post(cancelCmd); EventMsgUtil.getDefault().post(cancelCmd);
@ -104,15 +118,19 @@ public final class NormalController extends FeatureController implements INormal
* 重新下载 * 重新下载
*/ */
@Override @Override
public void reStart() { public long reStart() {
setAction(ACTION_RESTART);
if (checkConfig()) { if (checkConfig()) {
EventMsgUtil.getDefault() EventMsgUtil.getDefault()
.post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_RESTART, .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_RESTART,
checkTaskType())); checkTaskType()));
return getEntity().getId();
} }
return -1;
} }
@Override public void save() { @Override public void save() {
setAction(ACTION_SAVE);
if (!checkConfig()) { if (!checkConfig()) {
ALog.e(TAG, "保存修改失败"); ALog.e(TAG, "保存修改失败");
} else { } else {

@ -1,421 +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.common.ftp;
import android.net.TrafficStats;
import android.os.Process;
import android.text.TextUtils;
import aria.apache.commons.net.ftp.FTP;
import aria.apache.commons.net.ftp.FTPClient;
import aria.apache.commons.net.ftp.FTPClientConfig;
import aria.apache.commons.net.ftp.FTPFile;
import aria.apache.commons.net.ftp.FTPReply;
import aria.apache.commons.net.ftp.FTPSClient;
import com.arialyy.aria.core.AriaManager;
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.upload.UploadEntity;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.FileNotFoundException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.Regular;
import com.arialyy.aria.util.SSLContextUtil;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.SSLContext;
/**
* Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息
*/
public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
implements Runnable {
private final String TAG = "AbsFtpInfoThread";
protected ENTITY mEntity;
protected TASK_WRAPPER mTaskWrapper;
private FtpTaskConfig mTaskDelegate;
private int mConnectTimeOut;
protected OnFileInfoCallback mCallback;
protected long mSize = 0;
protected String charSet = "UTF-8";
private boolean isUpload = false;
public AbsFtpInfoThread(TASK_WRAPPER taskWrapper, OnFileInfoCallback callback) {
mTaskWrapper = taskWrapper;
mEntity = taskWrapper.getEntity();
mTaskDelegate = taskWrapper.asFtp();
mConnectTimeOut =
AriaManager.getInstance().getDownloadConfig().getConnectTimeOut();
mCallback = callback;
if (mEntity instanceof UploadEntity) {
isUpload = true;
}
}
/**
* 获取请求的远程文件路径
*
* @return 远程文件路径
*/
protected abstract String getRemotePath();
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode());
FTPClient client = null;
try {
client = createFtpClient();
if (client == null) {
ALog.e(TAG, String.format("任务【%s】失败", mTaskDelegate.getUrlEntity().url));
return;
}
String remotePath = CommonUtil.convertFtpChar(charSet, getRemotePath());
FTPFile[] files = client.listFiles(remotePath);
boolean isExist = files.length != 0;
if (!isExist && !isUpload) {
int i = remotePath.lastIndexOf(File.separator);
FTPFile[] files1;
if (i == -1) {
files1 = client.listFiles();
} else {
files1 = client.listFiles(remotePath.substring(0, i + 1));
}
if (files1.length > 0) {
ALog.i(TAG,
String.format("路径【%s】下的文件列表 ===================================", getRemotePath()));
for (FTPFile file : files1) {
ALog.d(TAG, file.toString());
}
ALog.i(TAG,
"================================= --end-- ===================================");
} else {
ALog.w(TAG, String.format("获取文件列表失败,msg:%s", client.getReplyString()));
}
closeClient(client);
failDownload(new FileNotFoundException(TAG,
String.format("文件不存在,url: %s, remotePath:%s", mTaskDelegate.getUrlEntity().url,
remotePath)), false);
return;
}
// 处理拦截功能
if (!onInterceptor(client, files)) {
closeClient(client);
ALog.d(TAG, "拦截器处理完成任务,任务将不再执行");
return;
}
//为了防止编码错乱,需要使用原始字符串
mSize = getFileSize(files, client, getRemotePath());
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
if (isUpload) {
//服务器上没有该文件路径,表示该任务为新的上传任务
mTaskWrapper.setNewTask(true);
} else {
closeClient(client);
failDownload(new AriaIOException(TAG,
String.format("获取文件信息错误,url: %s, errorCode:%s, errorMsg:%s",
mTaskDelegate.getUrlEntity().url, reply, client.getReplyString())), true);
return;
}
}
mTaskWrapper.setCode(reply);
if (mSize != 0 && !isUpload) {
mEntity.setFileSize(mSize);
}
onPreComplete(reply);
mEntity.update();
} catch (IOException e) {
failDownload(new AriaIOException(TAG,
String.format("FTP错误信息,code:%s,msg:%s", client.getReplyCode(), client.getReplyString()),
e), true);
} finally {
closeClient(client);
}
}
/**
* 处理拦截
*
* @param ftpFiles remotePath路径下的所有文件
* @return {@code false} 拦截器处理完成任务任务将不再执行{@code true} 拦截器处理任务完成任务任务继续执行
*/
protected boolean onInterceptor(FTPClient client, FTPFile[] ftpFiles) {
return true;
}
/**
* 检查文件是否存在
*
* @return {@code true}存在
*/
private boolean checkFileExist(FTPFile[] ftpFiles, String fileName) {
for (FTPFile ff : ftpFiles) {
if (ff.getName().equals(fileName)) {
return true;
}
}
return false;
}
protected void onPreComplete(int code) {
}
/**
* 创建FTP客户端
*/
private FTPClient createFtpClient() {
FTPClient client = null;
final FtpUrlEntity urlEntity = mTaskDelegate.getUrlEntity();
try {
Pattern p = Pattern.compile(Regular.REG_IP_V4);
Matcher m = p.matcher(urlEntity.hostName);
if (m.find() && m.groupCount() > 0) {
client = newInstanceClient(urlEntity);
client.setConnectTimeout(mConnectTimeOut); // 连接10s超时
InetAddress ip = InetAddress.getByName(urlEntity.hostName);
client = connect(client, new InetAddress[] { ip }, 0, Integer.parseInt(urlEntity.port));
mTaskDelegate.getUrlEntity().validAddr = ip;
} else {
DNSQueryThread dnsThread = new DNSQueryThread(urlEntity.hostName);
dnsThread.start();
dnsThread.join(mConnectTimeOut);
InetAddress[] ips = dnsThread.getIps();
client = connect(newInstanceClient(urlEntity), ips, 0, Integer.parseInt(urlEntity.port));
}
if (client == null) {
failDownload(new AriaIOException(TAG,
String.format("链接失败, url: %s", mTaskDelegate.getUrlEntity().url)), false);
return null;
}
boolean loginSuccess = true;
if (urlEntity.needLogin) {
try {
if (TextUtils.isEmpty(urlEntity.account)) {
loginSuccess = client.login(urlEntity.user, urlEntity.password);
} else {
loginSuccess = client.login(urlEntity.user, urlEntity.password, urlEntity.account);
}
} catch (IOException e) {
ALog.e(TAG,
new TaskException(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(),
client.getReplyString()), e));
return null;
}
}
if (!loginSuccess) {
failDownload(
new TaskException(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(),
client.getReplyString())),
false);
client.disconnect();
return null;
}
int reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
failDownload(new AriaIOException(TAG,
String.format("无法连接到ftp服务器,filePath: %s, url: %s, errorCode: %s, errorMsg:%s",
mEntity.getKey(), mTaskDelegate.getUrlEntity().url, reply,
client.getReplyString())),
true);
return null;
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码
charSet = "UTF-8";
reply = client.sendCommand("OPTS UTF8", "ON");
if (reply != FTPReply.COMMAND_IS_SUPERFLUOUS) {
ALog.i(TAG, "D_FTP 服务器不支持开启UTF8编码,尝试使用Aria手动设置的编码");
if (!TextUtils.isEmpty(mTaskWrapper.asFtp().getCharSet())) {
charSet = mTaskWrapper.asFtp().getCharSet();
}
}
client.setControlEncoding(charSet);
client.setDataTimeout(10 * 1000);
client.enterLocalPassiveMode();
client.setFileType(FTP.BINARY_FILE_TYPE);
} catch (IOException e) {
closeClient(client);
e.printStackTrace();
} catch (InterruptedException e) {
closeClient(client);
e.printStackTrace();
}
return client;
}
protected void closeClient(FTPClient client) {
try {
if (client != null && client.isConnected()) {
client.logout();
client.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 创建FTP/FTPS客户端
*/
private FTPClient newInstanceClient(FtpUrlEntity urlEntity) {
FTPClient temp;
if (urlEntity.isFtps) {
FTPSClient sClient;
SSLContext sslContext = SSLContextUtil.getSSLContext(urlEntity.keyAlias, urlEntity.storePath,
urlEntity.protocol);
if (sslContext == null) {
sClient = new FTPSClient(urlEntity.protocol, urlEntity.isImplicit);
} else {
sClient = new FTPSClient(true, sslContext);
}
temp = sClient;
} else {
temp = new FTPClient();
}
FTPClientConfig clientConfig;
if (mTaskWrapper.asFtp().getClientConfig() != null) {
clientConfig = mTaskWrapper.asFtp().getClientConfig();
} else {
clientConfig = new FTPClientConfig(FTPClientConfig.SYST_UNIX);
clientConfig.setServerLanguageCode("en");
}
temp.configure(clientConfig);
return temp;
}
/**
* 连接到ftp服务器
*/
private FTPClient connect(FTPClient client, InetAddress[] ips, int index, int port) {
if (ips == null || ips.length == 0) {
ALog.w(TAG, "无可用ip");
return null;
}
try {
client.setConnectTimeout(mConnectTimeOut); //需要先设置超时,这样才不会出现阻塞
client.connect(ips[index], port);
mTaskDelegate.getUrlEntity().validAddr = ips[index];
FtpUrlEntity urlEntity = mTaskWrapper.asFtp().getUrlEntity();
if (urlEntity.isFtps) {
FTPSClient sClient = (FTPSClient) client;
sClient.execPBSZ(0);
sClient.execPROT("P");
}
return client;
} catch (IOException e) {
e.printStackTrace();
closeClient(client);
if (index + 1 >= ips.length) {
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,已没有其他地址,链接失败;如果是ftps,请检查端口是否使用了ftp的端口而不是ftps的端口");
return null;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
ALog.w(TAG, "遇到[ECONNREFUSED-连接被服务器拒绝]错误,正在尝试下一个地址");
return connect(newInstanceClient(mTaskDelegate.getUrlEntity()), ips, index + 1, port);
}
}
/**
* 遍历FTP服务器上对应文件或文件夹大小
*
* @throws IOException 字符串编码转换错误
*/
private long getFileSize(FTPFile[] files, FTPClient client, String dirName) throws IOException {
long size = 0;
String path = dirName + "/";
for (FTPFile file : files) {
if (file.isFile()) {
size += file.getSize();
ALog.d(TAG, "isValid = " + file.isValid());
handleFile(path + file.getName(), file);
} else {
String remotePath = CommonUtil.convertFtpChar(charSet, path + file.getName());
size += getFileSize(client.listFiles(remotePath), client, path + file.getName());
}
}
return size;
}
/**
* 处理FTP文件信息
*
* @param remotePath ftp服务器文件夹路径
* @param ftpFile ftp服务器上对应的文件
*/
protected void handleFile(String remotePath, FTPFile ftpFile) {
}
protected void failDownload(BaseException e, boolean needRetry) {
if (mCallback != null) {
mCallback.onFail(mEntity, e, needRetry);
}
}
/**
* 获取可用IP的超时线程InetAddress.getByName没有超时功能需要自己处理超时
*/
private static class DNSQueryThread extends Thread {
private String hostName;
private InetAddress[] ips;
DNSQueryThread(String hostName) {
this.hostName = hostName;
}
@Override
public void run() {
try {
ips = InetAddress.getAllByName(hostName);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
synchronized InetAddress[] getIps() {
return ips;
}
}
}

@ -1,105 +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.common.ftp;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.common.ProtocolType;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
/**
* D_FTP SSL/TSL 参数委托
*/
public class FTPSDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
private FtpUrlEntity mUrlEntity;
public FTPSDelegate(TARGET target, AbsTaskWrapper wrapper) {
super(target, wrapper);
mUrlEntity = getTaskWrapper().asFtp().getUrlEntity();
mUrlEntity.isFtps = true;
}
/**
* 设置协议类型
*
* @param protocol {@link ProtocolType}
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FTPSDelegate<TARGET> setProtocol(@ProtocolType String protocol) {
if (TextUtils.isEmpty(protocol)) {
throw new NullPointerException("协议为空");
}
mUrlEntity.protocol = protocol;
return this;
}
/**
* 设置证书别名
*
* @param keyAlias 别名
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FTPSDelegate<TARGET> setAlias(String keyAlias) {
if (TextUtils.isEmpty(keyAlias)) {
throw new NullPointerException("别名为空");
}
mUrlEntity.keyAlias = keyAlias;
return this;
}
/**
* 设置证书密码
*
* @param storePass 私钥密码
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FTPSDelegate<TARGET> setStorePass(String storePass) {
if (TextUtils.isEmpty(storePass)) {
throw new NullPointerException("证书密码为空");
}
mUrlEntity.storePass = storePass;
return this;
}
/**
* 设置证书路径
*
* @param storePath 证书路径
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FTPSDelegate<TARGET> setStorePath(String storePath) {
if (TextUtils.isEmpty(storePath)) {
throw new NullPointerException("证书路径为空");
}
mUrlEntity.storePath = storePath;
return this;
}
/**
* 设置安全模式默认true
*
* @param isImplicit true 隐式false 显式
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FTPSDelegate<TARGET> setImplicit(boolean isImplicit) {
mUrlEntity.isImplicit = isImplicit;
return this;
}
}

@ -1,92 +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.common.ftp;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import aria.apache.commons.net.ftp.FTPClientConfig;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
/**
* Created by laoyuyu on 2018/3/9.
*/
public class FtpDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
private static final String TAG = "FtpDelegate";
public FtpDelegate(TARGET target, AbsTaskWrapper wrapper) {
super(target, wrapper);
}
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FtpDelegate<TARGET> charSet(String charSet) {
if (TextUtils.isEmpty(charSet)) {
throw new NullPointerException("字符编码为空");
}
getTaskWrapper().asFtp().setCharSet(charSet);
return this;
}
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FtpDelegate<TARGET> login(String userName, String password) {
return login(userName, password, null);
}
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FtpDelegate<TARGET> login(String userName, String password, String account) {
if (TextUtils.isEmpty(userName)) {
ALog.e(TAG, "用户名不能为null");
return this;
} else if (TextUtils.isEmpty(password)) {
ALog.e(TAG, "密码不能为null");
return this;
}
// urlEntity 不能在构造函数中获取,因为ftp上传时url是后于构造函数的
FtpUrlEntity urlEntity = getTaskWrapper().asFtp().getUrlEntity();
urlEntity.needLogin = true;
urlEntity.user = userName;
urlEntity.password = password;
urlEntity.account = account;
return this;
}
/**
* 是否是FTPS协议
* 如果是FTPS协议需要使用{@link FTPSDelegate#setStorePath(String)} {@link FTPSDelegate#setAlias(String)}
* 设置证书信息
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FTPSDelegate<TARGET> asFtps() {
return new FTPSDelegate<>(mTarget, mWrapper);
}
/**
* 配置ftp客户端信息
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public FtpDelegate<TARGET> setFtpClentConfig(FTPClientConfig config) {
getTaskWrapper().asFtp().setClientConfig(config);
return this;
}
//@Override public TARGET setProxy(Proxy proxy) {
// mTarget.getTaskWrapper().asFtp().setProxy(proxy);
// return mTarget;
//}
}

@ -1,102 +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.common.ftp;
import aria.apache.commons.net.ftp.FTPClientConfig;
import com.arialyy.aria.core.inf.ITaskConfig;
import java.lang.ref.SoftReference;
import java.net.Proxy;
/**
* fTP任务设置的信息用户名密码端口等信息
*/
public class FtpTaskConfig implements ITaskConfig {
/**
* 账号和密码
*/
private FtpUrlEntity urlEntity;
private Proxy proxy;
/**
* 字符编码默认为"utf-8"
*/
private String charSet = "utf-8";
/**
* 上传拦截器
*/
private SoftReference<IFtpUploadInterceptor> uploadInterceptor;
/**
* 上传到服务器文件的新文件名{@link FtpInterceptHandler#getNewFileName()}
*/
private String newFileName;
/**
* client配置信息
*/
private FTPClientConfig clientConfig;
public FTPClientConfig getClientConfig() {
return clientConfig;
}
public void setClientConfig(FTPClientConfig clientConfig) {
this.clientConfig = clientConfig;
}
public String getNewFileName() {
return newFileName;
}
public void setNewFileName(String newFileName) {
this.newFileName = newFileName;
}
public IFtpUploadInterceptor getUploadInterceptor() {
return uploadInterceptor.get();
}
public void setUploadInterceptor(IFtpUploadInterceptor uploadInterceptor) {
this.uploadInterceptor = new SoftReference<>(uploadInterceptor);
}
public FtpUrlEntity getUrlEntity() {
return urlEntity;
}
public void setUrlEntity(FtpUrlEntity urlEntity) {
this.urlEntity = urlEntity;
}
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
public Proxy getProxy() {
return proxy;
}
public String getCharSet() {
return charSet;
}
public void setCharSet(String charSet) {
this.charSet = charSet;
}
}

@ -1,52 +0,0 @@
package com.arialyy.aria.core.common.ftp;
import aria.apache.commons.net.ftp.FTPSClient;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.Locale;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
public class SSLSessionReuseFTPSClient extends FTPSClient {
SSLSessionReuseFTPSClient(boolean b, SSLContext context) {
super(b, context);
}
// adapted from:
// https://trac.cyberduck.io/browser/trunk/ftp/src/main/java/ch/cyberduck/core/ftp/FTPClient.java
@Override
protected void _prepareDataSocket_(final Socket socket) throws IOException {
if (socket instanceof SSLSocket) {
// Control socket is SSL
final SSLSession session = ((SSLSocket) _socket_).getSession();
if (session.isValid()) {
final SSLSessionContext context = session.getSessionContext();
try {
//final Field sessionHostPortCache = context.getClass().getDeclaredField("sessionHostPortCache");
final Field sessionHostPortCache =
context.getClass().getDeclaredField("sessionsByHostAndPort");
sessionHostPortCache.setAccessible(true);
final Object cache = sessionHostPortCache.get(context);
final Method method =
cache.getClass().getDeclaredMethod("put", Object.class, Object.class);
method.setAccessible(true);
method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostName(),
String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session);
method.invoke(cache, String.format("%s:%s", socket.getInetAddress().getHostAddress(),
String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT), session);
} catch (NoSuchFieldException e) {
throw new IOException(e);
} catch (Exception e) {
throw new IOException(e);
}
} else {
throw new IOException("Invalid SSL Session");
}
}
}
}

@ -1,186 +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.common.http;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import java.net.Proxy;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* HTTP协议处理
*/
public class HttpDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
public HttpDelegate(TARGET target, AbsTaskWrapper wrapper) {
super(target, wrapper);
}
/**
* 设置请求类型
*
* @param requestEnum {@link RequestEnum}
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> setRequestType(RequestEnum requestEnum) {
getTaskWrapper().asHttp().setRequestEnum(requestEnum);
return this;
}
/**
* 设置http请求参数
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> setParams(Map<String, String> params) {
getTaskWrapper().asHttp().setParams(params);
return this;
}
/**
* 设置http请求参数
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> setParam(String key, String value) {
if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) {
ALog.d(TAG, "key 或value 为空");
return this;
}
Map<String, String> params = getTaskWrapper().asHttp().getParams();
if (params == null) {
params = new HashMap<>();
getTaskWrapper().asHttp().setParams(params);
}
params.put(key, value);
return this;
}
/**
* 设置http表单字段
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> setFormFields(Map<String, String> params) {
getTaskWrapper().asHttp().setFormFields(params);
return this;
}
/**
* 给url请求添加Header数据
* 如果新的header数据和数据保存的不一致则更新数据库中对应的header数据
*
* @param key header对应的key
* @param value header对应的value
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> addHeader(@NonNull String key, @NonNull String value) {
if (TextUtils.isEmpty(key)) {
ALog.w(TAG, "设置header失败,header对应的key不能为null");
return this;
} else if (TextUtils.isEmpty(value)) {
ALog.w(TAG, "设置header失败,header对应的value不能为null");
return this;
}
addHeader(getTaskWrapper(), key, value);
return this;
}
/**
* 给url请求添加一组header数据
* 如果新的header数据和数据保存的不一致则更新数据库中对应的header数据
*
* @param headers 一组http header数据
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> addHeaders(@NonNull Map<String, String> headers) {
if (headers.size() == 0) {
ALog.w(TAG, "设置header失败,map没有header数据");
return this;
}
addHeaders(getTaskWrapper(), headers);
return this;
}
/**
* 设置代理
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public HttpDelegate<TARGET> setUrlProxy(Proxy proxy) {
getTaskWrapper().asHttp().setProxy(proxy);
return this;
}
private void addHeader(AbsTaskWrapper taskWrapper, String key, String value) {
HttpTaskConfig taskDelegate = taskWrapper.asHttp();
if (taskDelegate.getHeaders().get(key) == null) {
taskDelegate.getHeaders().put(key, value);
} else if (!taskDelegate.getHeaders().get(key).equals(value)) {
taskDelegate.getHeaders().put(key, value);
}
}
private void addHeaders(AbsTaskWrapper taskWrapper, Map<String, String> headers) {
HttpTaskConfig taskDelegate = taskWrapper.asHttp();
/*
两个map比较逻辑
1比对key是否相同
2如果key相同比对value是否相同
3只有当上面两个步骤中key value都相同时才能任务两个map数据一致
*/
boolean mapEquals = false;
if (taskDelegate.getHeaders().size() == headers.size()) {
int i = 0;
Set<String> keys = taskDelegate.getHeaders().keySet();
for (String key : keys) {
if (headers.containsKey(key)) {
i++;
} else {
break;
}
}
if (i == taskDelegate.getHeaders().size()) {
int j = 0;
Collection<String> values = taskDelegate.getHeaders().values();
for (String value : values) {
if (headers.containsValue(value)) {
j++;
} else {
break;
}
}
if (j == taskDelegate.getHeaders().size()) {
mapEquals = true;
}
}
}
if (!mapEquals) {
taskDelegate.getHeaders().clear();
Set<String> keys = headers.keySet();
for (String key : keys) {
taskDelegate.getHeaders().put(key, headers.get(key));
}
}
}
}

@ -16,14 +16,16 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.common.controller.FeatureController;
import com.arialyy.aria.core.download.m3u8.M3U8Entity;
import com.arialyy.aria.core.inf.ICheckEntityUtil; import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.core.inf.ITargetHandler; import com.arialyy.aria.core.inf.ITargetHandler;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.RecordUtil; import com.arialyy.aria.util.RecordUtil;
import java.io.File; import java.io.File;
@ -31,15 +33,17 @@ import java.io.File;
* 检查下载任务实体 * 检查下载任务实体
*/ */
public class CheckDEntityUtil implements ICheckEntityUtil { public class CheckDEntityUtil implements ICheckEntityUtil {
private final String TAG = "CheckDLoadEntity"; private final String TAG = CommonUtil.getClassName(getClass());
private DTaskWrapper mWrapper; private DTaskWrapper mWrapper;
private DownloadEntity mEntity; private DownloadEntity mEntity;
private int action;
public static CheckDEntityUtil newInstance(DTaskWrapper wrapper) { public static CheckDEntityUtil newInstance(DTaskWrapper wrapper, int action) {
return new CheckDEntityUtil(wrapper); return new CheckDEntityUtil(wrapper, action);
} }
private CheckDEntityUtil(DTaskWrapper wrapper) { private CheckDEntityUtil(DTaskWrapper wrapper, int action) {
this.action = action;
mWrapper = wrapper; mWrapper = wrapper;
mEntity = mWrapper.getEntity(); mEntity = mWrapper.getEntity();
} }
@ -47,11 +51,11 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
@Override @Override
public boolean checkEntity() { public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) { if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg); ALog.e(TAG, String.format("任务操作失败,%s", mWrapper.getErrorEvent().errorMsg));
return false; return false;
} }
boolean b = checkFtps() && checkUrl() && checkFilePath(); boolean b = checkUrl() && checkFilePath();
if (b) { if (b) {
mEntity.save(); mEntity.save();
} }
@ -64,36 +68,39 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
private void handleM3U8() { private void handleM3U8() {
File file = new File(mWrapper.getTempFilePath()); File file = new File(mWrapper.getTempFilePath());
// 缓存文件夹格式:问文件夹/.文件名_码率 Object bw = mWrapper.getM3U8Params().getParam(IOptionConstant.bandWidth);
String cacheDir = String.format("%s/.%s_%s", file.getParent(), file.getName(), int bandWidth = bw == null ? 0 : (int) bw;
mWrapper.asM3U8().getBandWidth()); String cacheDir = FileUtil.getTsCacheDir(file.getPath(), bandWidth);
mWrapper.asM3U8().setCacheDir(cacheDir);
mWrapper.getM3U8Params().setParams(IOptionConstant.cacheDir, cacheDir);
M3U8Entity m3U8Entity = mEntity.getM3U8Entity(); M3U8Entity m3U8Entity = mEntity.getM3U8Entity();
if (m3U8Entity == null) { if (m3U8Entity == null) {
m3U8Entity = new M3U8Entity(); m3U8Entity = new M3U8Entity();
m3U8Entity.setFilePath(mEntity.getFilePath()); m3U8Entity.setFilePath(mEntity.getFilePath());
m3U8Entity.setPeerIndex(0); m3U8Entity.setPeerIndex(0);
m3U8Entity.setCacheDir(cacheDir); m3U8Entity.setCacheDir(cacheDir);
m3U8Entity.setGenerateIndexFile(mWrapper.asM3U8().isGenerateIndexFileTemp());
m3U8Entity.insert(); m3U8Entity.insert();
} else { } else {
m3U8Entity.setGenerateIndexFile(mWrapper.asM3U8().isGenerateIndexFileTemp());
m3U8Entity.update(); m3U8Entity.update();
} }
if (mWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) { if (mWrapper.getRequestType() == ITaskWrapper.M3U8_VOD
&& action == FeatureController.ACTION_CREATE) {
if (mEntity.getFileSize() == 0) { if (mEntity.getFileSize() == 0) {
ALog.w(TAG, ALog.w(TAG,
"由于m3u8协议的特殊性质,无法有效快速获取到正确到文件长度,如果你需要显示文件中长度,你需要自行设置文件长度:.asM3U8().asVod().setFileSize(xxx)"); "由于m3u8协议的特殊性质,无法有效快速获取到正确到文件长度,如果你需要显示文件中长度,你需要自行设置文件长度:.asM3U8().asVod().setFileSize(xxx)");
} }
} else if (mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { } else if (mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE
&& action != FeatureController.ACTION_CANCEL) {
if (file.exists()) { if (file.exists()) {
ALog.w(TAG, "对于直播来说,每次下载都是一个新文件,所以你需要设置新都文件路径,否则Aria框架将会覆盖已下载的文件"); ALog.w(TAG, "对于直播来说,每次下载都是一个新文件,所以你需要设置新都文件路径,否则Aria框架将会覆盖已下载的文件");
file.delete(); file.delete();
} }
} }
if (mWrapper.asM3U8().getBandWidthUrlConverter() != null if (action != FeatureController.ACTION_CANCEL
&& mWrapper.asM3U8().getBandWidth() == 0) { && mWrapper.getM3U8Params().getHandler(IOptionConstant.bandWidthUrlConverter) != null
&& bandWidth == 0) {
ALog.w(TAG, "你已经设置了码率url转换器,但是没有设置码率,Aria框架将采用第一个获取到的码率"); ALog.w(TAG, "你已经设置了码率url转换器,但是没有设置码率,Aria框架将采用第一个获取到的码率");
} }
} }
@ -103,7 +110,12 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
if (TextUtils.isEmpty(filePath)) { if (TextUtils.isEmpty(filePath)) {
ALog.e(TAG, "下载失败,文件保存路径为null"); ALog.e(TAG, "下载失败,文件保存路径为null");
return false; return false;
} else if (!filePath.startsWith("/")) { }
if (!FileUtil.canWrite(new File(filePath).getParent())){
ALog.e(TAG, String.format("路径【%s】不可写", filePath));
return false;
}
if (!filePath.startsWith("/")) {
ALog.e(TAG, String.format("下载失败,文件保存路径【%s】错误", filePath)); ALog.e(TAG, String.format("下载失败,文件保存路径【%s】错误", filePath));
return false; return false;
} }
@ -129,19 +141,22 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
/** /**
* 检查路径冲突 * 检查路径冲突
* @return true 路径没有冲突
*/ */
private boolean checkPathConflicts(String filePath) { private boolean checkPathConflicts(String filePath) {
DownloadEntity de = DbEntity.findFirst(DownloadEntity.class, "downloadPath=?", filePath);
if (de != null && de.getUrl().equals(mEntity.getUrl())){
mEntity.rowID = de.rowID;
mEntity.setFilePath(filePath);
mEntity.setFileName(new File(filePath).getName());
return true;
}
//设置文件保存路径,如果新文件路径和旧文件路径不同,则修改路径 //设置文件保存路径,如果新文件路径和旧文件路径不同,则修改路径
if (!filePath.equals(mEntity.getFilePath())) { if (!filePath.equals(mEntity.getFilePath())) {
// 检查路径冲突 // 检查路径冲突
if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", filePath)) { if (!CheckUtil.checkDPathConflicts(mWrapper.isIgnoreFilePathOccupy(), filePath,
if (!mWrapper.isForceDownload()) { mWrapper.getRequestType())) {
ALog.e(TAG, String.format("下载失败,保存路径【%s】已经被其它任务占用,请设置其它保存路径", filePath));
return false; return false;
} else {
ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath));
RecordUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD);
}
} }
File newFile = new File(filePath); File newFile = new File(filePath);
@ -149,19 +164,19 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
mEntity.setFileName(newFile.getName()); mEntity.setFileName(newFile.getName());
// 如过使用Content-Disposition中的文件名,将不会执行重命名工作 // 如过使用Content-Disposition中的文件名,将不会执行重命名工作
if (mWrapper.asHttp().isUseServerFileName() Object usf = mWrapper.getOptionParams().getParam(IOptionConstant.useServerFileName);
|| mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { if ((usf != null && (boolean) usf) || mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
return true; return true;
} }
if (!TextUtils.isEmpty(mEntity.getFilePath())) { if (!TextUtils.isEmpty(mEntity.getFilePath())) {
File oldFile = new File(mEntity.getFilePath()); File oldFile = new File(mEntity.getFilePath());
if (oldFile.exists()) { if (oldFile.exists()) {
// 处理普通任务的重命名 // 处理普通任务的重命名
RecordUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath()); RecordUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath(), mEntity.getTaskType());
ALog.i(TAG, String.format("将任务重命名为:%s", newFile.getName())); ALog.i(TAG, String.format("将任务重命名为:%s", newFile.getName()));
} else if (RecordUtil.blockTaskExists(oldFile.getPath())) { } else if (RecordUtil.blockTaskExists(oldFile.getPath())) {
// 处理分块任务的重命名 // 处理分块任务的重命名
RecordUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath()); RecordUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath(), mEntity.getTaskType());
ALog.i(TAG, String.format("将分块任务重命名为:%s", newFile.getName())); ALog.i(TAG, String.format("将分块任务重命名为:%s", newFile.getName()));
} }
} }
@ -174,7 +189,7 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
if (TextUtils.isEmpty(url)) { if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "下载失败,url为null"); ALog.e(TAG, "下载失败,url为null");
return false; return false;
} else if (!CheckUtil.checkUrlNotThrow(url)) { } else if (!CheckUtil.checkUrl(url)) {
ALog.e(TAG, "下载失败,url【" + url + "】错误"); ALog.e(TAG, "下载失败,url【" + url + "】错误");
return false; return false;
} }
@ -188,16 +203,4 @@ public class CheckDEntityUtil implements ICheckEntityUtil {
} }
return true; return true;
} }
private boolean checkFtps() {
//if (mWrapper.getRequestType() == ITaskWrapper.D_FTP && mWrapper.asFtp()
// .getUrlEntity().isFtps) {
// String ftpUrl = mEntity.getUrl();
// if (!ftpUrl.startsWith("ftps") && !ftpUrl.startsWith("sftp")) {
// ALog.e(TAG, String.format("地址【%s】错误,ftps地址开头必须是:ftps或sftp", ftpUrl));
// return false;
// }
//}
return true;
}
} }

@ -17,10 +17,13 @@ package com.arialyy.aria.core.download;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.common.RequestEnum; import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.common.controller.FeatureController;
import com.arialyy.aria.core.inf.ICheckEntityUtil; import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.RecordUtil; import com.arialyy.aria.util.RecordUtil;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,12 +42,17 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
* 是否需要修改路径 * 是否需要修改路径
*/ */
private boolean needModifyPath = false; private boolean needModifyPath = false;
private int action;
public static CheckDGEntityUtil newInstance(DGTaskWrapper wrapper) { /**
return new CheckDGEntityUtil(wrapper); * @param action {@link FeatureController#ACTION_CREATE}
*/
public static CheckDGEntityUtil newInstance(DGTaskWrapper wrapper, int action) {
return new CheckDGEntityUtil(wrapper, action);
} }
private CheckDGEntityUtil(DGTaskWrapper wrapper) { private CheckDGEntityUtil(DGTaskWrapper wrapper, int action) {
this.action = action;
mWrapper = wrapper; mWrapper = wrapper;
mEntity = mWrapper.getEntity(); mEntity = mWrapper.getEntity();
} }
@ -55,34 +63,41 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
* @return {@code true} 合法 * @return {@code true} 合法
*/ */
private boolean checkDirPath() { private boolean checkDirPath() {
if (TextUtils.isEmpty(mWrapper.getDirPathTemp())) { String dirPath = mWrapper.getDirPathTemp();
if (TextUtils.isEmpty(dirPath)) {
ALog.e(TAG, "文件夹路径不能为null"); ALog.e(TAG, "文件夹路径不能为null");
return false; return false;
} else if (!mWrapper.getDirPathTemp().startsWith("/")) { }
ALog.e(TAG, String.format("文件夹路径【%s】错误", mWrapper.getDirPathTemp())); File file = new File(dirPath);
if (!FileUtil.canWrite(file.getParent()) && !FileUtil.canWrite(dirPath)) {
ALog.e(TAG, String.format("路径【%s】不可写", dirPath));
return false; return false;
} }
File file = new File(mWrapper.getDirPathTemp()); if (!dirPath.startsWith("/")) {
ALog.e(TAG, String.format("文件夹路径【%s】错误", dirPath));
return false;
}
if (file.isFile()) { if (file.isFile()) {
ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", mWrapper.getDirPathTemp())); ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", dirPath));
return false; return false;
} }
if ((mEntity.getDirPath() == null || !mEntity.getDirPath().equals(mWrapper.getDirPathTemp())) // 检查路径冲突
&& DbEntity.checkDataExist(DownloadGroupEntity.class, "dirPath=?", if (mWrapper.isNewTask() && !CheckUtil.checkDGPathConflicts(mWrapper.isIgnoreFilePathOccupy(),
mWrapper.getDirPathTemp())) { dirPath)) {
ALog.e(TAG, String.format("文件夹路径【%s】已被其它任务占用,请重新设置文件夹路径", mWrapper.getDirPathTemp()));
return false; return false;
} }
if (TextUtils.isEmpty(mEntity.getDirPath()) || !mEntity.getDirPath() if (TextUtils.isEmpty(mEntity.getDirPath()) || !mEntity.getDirPath()
.equals(mWrapper.getDirPathTemp())) { .equals(dirPath)) {
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
needModifyPath = true; needModifyPath = true;
mEntity.setDirPath(mWrapper.getDirPathTemp()); mEntity.setDirPath(dirPath);
ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", mWrapper.getDirPathTemp())); ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", dirPath));
} }
return true; return true;
} }
@ -109,10 +124,43 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
} }
} }
/**
* 检查和处理组合任务的路径冲突
*
* @param isIgnoreTaskOccupy true如果hash冲突将删除其它任务的记录的
* @param groupHash 组任务hash
* @return false 任务不再执行true 任务继续执行
*/
private boolean checkGroupHash(boolean isIgnoreTaskOccupy, String groupHash) {
DownloadGroupEntity dge =
DbEntity.findFirst(DownloadGroupEntity.class, "groupHash=?", groupHash);
if (dge != null && dge.getGroupHash().equals(mEntity.getGroupHash())) {
mEntity.rowID = dge.rowID;
return true;
}
if (DbEntity.checkDataExist(DownloadGroupEntity.class, "groupHash=?", groupHash)) {
if (!isIgnoreTaskOccupy) {
ALog.e(TAG, String.format("下载失败,数据库中已存在相同的url的组任务,groupHash = %s", groupHash));
return false;
} else {
ALog.w(TAG, String.format("数据库中已存在相同的url的组任务,将删除groupHash = %s 的旧任务", groupHash));
RecordUtil.delGroupTaskRecordByHash(groupHash, true);
return true;
}
}
return true;
}
@Override @Override
public boolean checkEntity() { public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) { if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg); ALog.e(TAG, String.format("任务操作失败,%s", mWrapper.getErrorEvent().errorMsg));
return false;
}
if ((action == FeatureController.ACTION_CREATE || action == FeatureController.ACTION_ADD)
&& !checkGroupHash(mWrapper.isIgnoreTaskOccupy(), mEntity.getGroupHash())) {
return false; return false;
} }
@ -128,14 +176,16 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
return false; return false;
} }
if (!mWrapper.isUnknownSize() && mEntity.getFileSize() == 0) { if (action != FeatureController.ACTION_CANCEL
&& !mWrapper.isUnknownSize()
&& mEntity.getFileSize() == 0) {
ALog.e(TAG, "组合任务必须设置文件文件大小,默认需要强制设置文件大小。如果无法获取到总长度,请调用#unknownSize()来标志该组合任务"); ALog.e(TAG, "组合任务必须设置文件文件大小,默认需要强制设置文件大小。如果无法获取到总长度,请调用#unknownSize()来标志该组合任务");
return false; return false;
} }
if (mWrapper.asHttp().getRequestEnum() == RequestEnum.POST) { if (mWrapper.getOptionParams().getParam(IOptionConstant.requestEnum) == RequestEnum.POST) {
for (DTaskWrapper subTask : mWrapper.getSubTaskWrapper()) { for (DTaskWrapper subWrapper : mWrapper.getSubTaskWrapper()) {
subTask.asHttp().setRequestEnum(RequestEnum.POST); subWrapper.getOptionParams().setParams(IOptionConstant.requestEnum, RequestEnum.POST);
} }
} }
@ -168,13 +218,12 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
if (!newName.equals(entity.getFileName())) { if (!newName.equals(entity.getFileName())) {
String oldPath = mEntity.getDirPath() + "/" + entity.getFileName(); String oldPath = mEntity.getDirPath() + "/" + entity.getFileName();
String newPath = mEntity.getDirPath() + "/" + newName; String newPath = mEntity.getDirPath() + "/" + newName;
if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=? or isComplete='true'", if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", newPath)) {
newPath)) { ALog.w(TAG, String.format("更新文件名失败,路径【%s】被其它任务占用", newPath));
ALog.w(TAG, String.format("更新文件名失败,路径【%s】已存在或文件已下载", newPath));
return; return;
} }
RecordUtil.modifyTaskRecord(oldPath, newPath); RecordUtil.modifyTaskRecord(oldPath, newPath, mEntity.getTaskType());
entity.setFilePath(newPath); entity.setFilePath(newPath);
entity.setFileName(newName); entity.setFileName(newName);
} }
@ -190,7 +239,7 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
*/ */
private boolean checkUrls() { private boolean checkUrls() {
if (mEntity.getUrls().isEmpty()) { if (mEntity.getUrls().isEmpty()) {
ALog.e(TAG, "下载失败,子任务下载列表为null"); ALog.e(TAG, "操作失败,子任务下载列表为null");
return false; return false;
} }
@ -235,9 +284,6 @@ public class CheckDGEntityUtil implements ICheckEntityUtil {
mWrapper.getSubNameTemp().remove(index); mWrapper.getSubNameTemp().remove(index);
} }
} }
mEntity.setGroupHash(CommonUtil.getMd5Code(mEntity.getUrls()));
return true; return true;
} }

@ -16,21 +16,25 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.inf.ICheckEntityUtil; import com.arialyy.aria.core.inf.ICheckEntityUtil;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import java.io.File; import java.io.File;
public class CheckFtpDirEntityUtil implements ICheckEntityUtil { public class CheckFtpDirEntityUtil implements ICheckEntityUtil {
private final String TAG = "CheckFtpDirEntityUtil"; private final String TAG = "CheckFtpDirEntityUtil";
private DGTaskWrapper mWrapper; private DGTaskWrapper mWrapper;
private DownloadGroupEntity mEntity; private DownloadGroupEntity mEntity;
private int action;
public static CheckFtpDirEntityUtil newInstance(DGTaskWrapper wrapper) { public static CheckFtpDirEntityUtil newInstance(DGTaskWrapper wrapper, int action) {
return new CheckFtpDirEntityUtil(wrapper); return new CheckFtpDirEntityUtil(wrapper, action);
} }
private CheckFtpDirEntityUtil(DGTaskWrapper wrapper) { private CheckFtpDirEntityUtil(DGTaskWrapper wrapper, int action) {
this.action = action;
mWrapper = wrapper; mWrapper = wrapper;
mEntity = mWrapper.getEntity(); mEntity = mWrapper.getEntity();
} }
@ -41,33 +45,34 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil {
* @return {@code true} 合法 * @return {@code true} 合法
*/ */
private boolean checkDirPath() { private boolean checkDirPath() {
if (TextUtils.isEmpty(mWrapper.getDirPathTemp())) { String dirPath = mWrapper.getDirPathTemp();
if (TextUtils.isEmpty(dirPath)) {
ALog.e(TAG, "文件夹路径不能为null"); ALog.e(TAG, "文件夹路径不能为null");
return false; return false;
} else if (!mWrapper.getDirPathTemp().startsWith("/")) { }
ALog.e(TAG, String.format("文件夹路径【%s】错误", mWrapper.getDirPathTemp())); if (!dirPath.startsWith("/")) {
ALog.e(TAG, String.format("文件夹路径【%s】错误", dirPath));
return false; return false;
} }
File file = new File(mWrapper.getDirPathTemp()); File file = new File(dirPath);
if (file.isFile()) { if (file.isFile()) {
ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", mWrapper.getDirPathTemp())); ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", dirPath));
return false; return false;
} }
if ((mEntity.getDirPath() == null || !mEntity.getDirPath().equals(mWrapper.getDirPathTemp())) // 检查路径冲突
&& DbEntity.checkDataExist(DownloadGroupEntity.class, "dirPath=?", if (mWrapper.isNewTask() && !CheckUtil.checkDGPathConflicts(mWrapper.isIgnoreFilePathOccupy(),
mWrapper.getDirPathTemp())) { dirPath)) {
ALog.e(TAG, String.format("文件夹路径【%s】已被其它任务占用,请重新设置文件夹路径", mWrapper.getDirPathTemp()));
return false; return false;
} }
if (TextUtils.isEmpty(mEntity.getDirPath()) || !mEntity.getDirPath() if (TextUtils.isEmpty(mEntity.getDirPath()) || !mEntity.getDirPath()
.equals(mWrapper.getDirPathTemp())) { .equals(dirPath)) {
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
mEntity.setDirPath(mWrapper.getDirPathTemp()); mEntity.setDirPath(dirPath);
ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", mWrapper.getDirPathTemp())); ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", dirPath));
} }
return true; return true;
} }
@ -75,7 +80,7 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil {
@Override @Override
public boolean checkEntity() { public boolean checkEntity() {
if (mWrapper.getErrorEvent() != null) { if (mWrapper.getErrorEvent() != null) {
ALog.e(TAG, mWrapper.getErrorEvent().errorMsg); ALog.e(TAG, String.format("任务操作失败,%s", mWrapper.getErrorEvent().errorMsg));
return false; return false;
} }
@ -83,12 +88,15 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil {
if (b) { if (b) {
mEntity.save(); mEntity.save();
} }
if (mWrapper.asFtp().getUrlEntity().isFtps) { FtpUrlEntity urlEntity =
if (TextUtils.isEmpty(mWrapper.asFtp().getUrlEntity().storePath)) { (FtpUrlEntity) mWrapper.getOptionParams().getParam(IOptionConstant.ftpUrlEntity);
assert urlEntity != null;
if (urlEntity.isFtps) {
if (TextUtils.isEmpty(urlEntity.idEntity.storePath)) {
ALog.e(TAG, "证书路径为空"); ALog.e(TAG, "证书路径为空");
return false; return false;
} }
if (TextUtils.isEmpty(mWrapper.asFtp().getUrlEntity().keyAlias)) { if (TextUtils.isEmpty(urlEntity.idEntity.keyAlias)) {
ALog.e(TAG, "证书别名为空"); ALog.e(TAG, "证书别名为空");
return false; return false;
} }

@ -0,0 +1,26 @@
/*
* 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 com.arialyy.aria.core.scheduler.NormalTaskListenerInterface;
import com.arialyy.aria.core.task.DownloadGroupTask;
/**
* @author ChenFei(chenfei0928 @ gmail.com)
* Date: 2020-07-07 14:12
*/
public interface DownloadGroupTaskListener extends NormalTaskListenerInterface<DownloadGroupTask> {
}

@ -15,35 +15,39 @@
*/ */
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.arialyy.annotations.TaskEnum; import com.arialyy.annotations.TaskEnum;
import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.command.CancelAllCmd; import com.arialyy.aria.core.command.CancelAllCmd;
import com.arialyy.aria.core.command.CmdHelper;
import com.arialyy.aria.core.command.NormalCmdFactory; import com.arialyy.aria.core.command.NormalCmdFactory;
import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.ProxyHelper; import com.arialyy.aria.core.common.ProxyHelper;
import com.arialyy.aria.core.download.group.FtpDirBuilderTarget; import com.arialyy.aria.core.download.target.DTargetFactory;
import com.arialyy.aria.core.download.group.FtpDirNormalTarget; import com.arialyy.aria.core.download.target.FtpBuilderTarget;
import com.arialyy.aria.core.download.group.GroupBuilderTarget; import com.arialyy.aria.core.download.target.FtpDirBuilderTarget;
import com.arialyy.aria.core.download.group.GroupNormalTarget; import com.arialyy.aria.core.download.target.FtpDirNormalTarget;
import com.arialyy.aria.core.download.group.GroupTargetFactory; import com.arialyy.aria.core.download.target.FtpNormalTarget;
import com.arialyy.aria.core.download.normal.FtpBuilderTarget; import com.arialyy.aria.core.download.target.GroupBuilderTarget;
import com.arialyy.aria.core.download.normal.FtpNormalTarget; import com.arialyy.aria.core.download.target.GroupNormalTarget;
import com.arialyy.aria.core.download.normal.HttpBuilderTarget; import com.arialyy.aria.core.download.target.HttpBuilderTarget;
import com.arialyy.aria.core.download.normal.HttpNormalTarget; import com.arialyy.aria.core.download.target.HttpNormalTarget;
import com.arialyy.aria.core.download.normal.DNormalTargetFactory;
import com.arialyy.aria.core.event.EventMsgUtil; import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsReceiver; import com.arialyy.aria.core.inf.AbsReceiver;
import com.arialyy.aria.core.inf.ITask;
import com.arialyy.aria.core.inf.ReceiverType; import com.arialyy.aria.core.inf.ReceiverType;
import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.scheduler.M3U8PeerTaskListener;
import com.arialyy.aria.core.scheduler.SubTaskListener;
import com.arialyy.aria.core.scheduler.TaskInternalListenerInterface;
import com.arialyy.aria.core.scheduler.TaskSchedulers; import com.arialyy.aria.core.scheduler.TaskSchedulers;
import com.arialyy.aria.core.task.ITask;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.ComponentUtil;
import com.arialyy.aria.util.DbDataHelper; import com.arialyy.aria.util.DbDataHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -54,7 +58,10 @@ import java.util.Set;
* 下载功能接收器 * 下载功能接收器
*/ */
public class DownloadReceiver extends AbsReceiver { public class DownloadReceiver extends AbsReceiver {
private final String TAG = "DownloadReceiver";
public DownloadReceiver(Object obj) {
super(obj);
}
/** /**
* 设置最大下载速度单位kb * 设置最大下载速度单位kb
@ -64,7 +71,7 @@ public class DownloadReceiver extends AbsReceiver {
*/ */
@Deprecated @Deprecated
public DownloadReceiver setMaxSpeed(int maxSpeed) { public DownloadReceiver setMaxSpeed(int maxSpeed) {
AriaManager.getInstance().getDownloadConfig().setMaxSpeed(maxSpeed); AriaConfig.getInstance().getDConfig().setMaxSpeed(maxSpeed);
return this; return this;
} }
@ -73,11 +80,10 @@ public class DownloadReceiver extends AbsReceiver {
* *
* @param url 下载地址 * @param url 下载地址
*/ */
@CheckResult public HttpBuilderTarget load(String url) {
public HttpBuilderTarget load(@NonNull String url) { ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
CheckUtil.checkUrlInvalidThrow(url); return DTargetFactory.getInstance()
return DNormalTargetFactory.getInstance() .generateBuilderTarget(HttpBuilderTarget.class, url);
.generateBuilderTarget(HttpBuilderTarget.class, url, targetName);
} }
/** /**
@ -86,11 +92,10 @@ public class DownloadReceiver extends AbsReceiver {
* @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link * @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link
* AbsEntity#getId()}读取任务id * AbsEntity#getId()}读取任务id
*/ */
@CheckResult
public HttpNormalTarget load(long taskId) { public HttpNormalTarget load(long taskId) {
CheckUtil.checkTaskId(taskId); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
return DNormalTargetFactory.getInstance() return DTargetFactory.getInstance()
.generateNormalTarget(HttpNormalTarget.class, taskId, targetName); .generateNormalTarget(HttpNormalTarget.class, taskId);
} }
/** /**
@ -98,33 +103,32 @@ public class DownloadReceiver extends AbsReceiver {
* *
* @param urls 组合任务只任务列被如果任务组的中的下载地址改变了则任务从新的一个任务组 * @param urls 组合任务只任务列被如果任务组的中的下载地址改变了则任务从新的一个任务组
*/ */
@CheckResult
public GroupBuilderTarget loadGroup(List<String> urls) { public GroupBuilderTarget loadGroup(List<String> urls) {
CheckUtil.checkDownloadUrls(urls); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
return GroupTargetFactory.getInstance().generateGroupBuilderTarget(urls, targetName); return DTargetFactory.getInstance().generateGroupBuilderTarget(urls);
} }
/** /**
* 加载组合任务用于任务停止删除等操作 * 加载组合任务用于任务停止删除等操作
* *
* @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link * @param taskId 任务id可从{@link AbsBuilderTarget#create}{@link AbsBuilderTarget#add}{@link
* * AbsEntity#getId()}读取任务id * AbsEntity#getId}读取任务id
*/ */
@CheckResult
public GroupNormalTarget loadGroup(long taskId) { public GroupNormalTarget loadGroup(long taskId) {
CheckUtil.checkTaskId(taskId); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP);
return GroupTargetFactory.getInstance() return DTargetFactory.getInstance()
.generateNormalTarget(GroupNormalTarget.class, taskId, targetName); .generateNormalTarget(GroupNormalTarget.class, taskId);
} }
/** /**
* 加载ftp单任务下载地址用于任务第一次下载如果需要控制任务停止或删除等操作请使用{@link #loadFtp(long)} * 加载ftp单任务下载地址用于任务第一次下载如果需要控制任务停止或删除等操作请使用{@link #loadFtp(long)}
*/ */
@CheckResult
public FtpBuilderTarget loadFtp(@NonNull String url) { public FtpBuilderTarget loadFtp(String url) {
CheckUtil.checkUrlInvalidThrow(url); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
return DNormalTargetFactory.getInstance() return DTargetFactory.getInstance()
.generateBuilderTarget(FtpBuilderTarget.class, url, targetName); .generateBuilderTarget(FtpBuilderTarget.class, url);
} }
/** /**
@ -133,20 +137,20 @@ public class DownloadReceiver extends AbsReceiver {
* @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link * @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link
* AbsEntity#getId()}读取任务id * AbsEntity#getId()}读取任务id
*/ */
@CheckResult
public FtpNormalTarget loadFtp(long taskId) { public FtpNormalTarget loadFtp(long taskId) {
CheckUtil.checkTaskId(taskId); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
return DNormalTargetFactory.getInstance() return DTargetFactory.getInstance()
.generateNormalTarget(FtpNormalTarget.class, taskId, targetName); .generateNormalTarget(FtpNormalTarget.class, taskId);
} }
/** /**
* 加载ftp文件夹下载地址用于任务第一次下载如果需要控制任务停止或删除等操作请使用{@link #loadFtpDir(long)} * 加载ftp文件夹下载地址用于任务第一次下载如果需要控制任务停止或删除等操作请使用{@link #loadFtpDir(long)}
*/ */
@CheckResult
public FtpDirBuilderTarget loadFtpDir(@NonNull String dirUrl) { public FtpDirBuilderTarget loadFtpDir(String dirUrl) {
CheckUtil.checkUrlInvalidThrow(dirUrl); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
return GroupTargetFactory.getInstance().generateDirBuilderTarget(dirUrl, targetName); return DTargetFactory.getInstance().generateDirBuilderTarget(dirUrl);
} }
/** /**
@ -155,26 +159,38 @@ public class DownloadReceiver extends AbsReceiver {
* @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link * @param taskId 任务id可从{@link AbsBuilderTarget#create()}{@link AbsBuilderTarget#add()}{@link
* AbsEntity#getId()}读取任务id * AbsEntity#getId()}读取任务id
*/ */
@CheckResult
public FtpDirNormalTarget loadFtpDir(long taskId) { public FtpDirNormalTarget loadFtpDir(long taskId) {
CheckUtil.checkTaskId(taskId); ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP);
return GroupTargetFactory.getInstance() return DTargetFactory.getInstance()
.generateNormalTarget(FtpDirNormalTarget.class, taskId, targetName); .generateNormalTarget(FtpDirNormalTarget.class, taskId);
} }
/** /**
* 将当前类注册到Aria * 将当前类注册到Aria
*/ */
public void register() { public void register() {
if (TextUtils.isEmpty(targetName)) { if (obj == null) {
ALog.e(TAG, "download register target null"); ALog.e(TAG, String.format("register【%s】观察者为空", getTargetName()));
return; return;
} }
Object obj = OBJ_MAP.get(getKey()); if (obj instanceof TaskInternalListenerInterface){
if (obj == null) { ProxyHelper.getInstance().checkProxyType(obj.getClass());
ALog.e(TAG, String.format("【%s】观察者为空", targetName)); if (obj instanceof DownloadTaskListener){
TaskSchedulers.getInstance().register(obj, TaskEnum.DOWNLOAD);
}
if (obj instanceof DownloadGroupTaskListener){
TaskSchedulers.getInstance().register(obj, TaskEnum.DOWNLOAD_GROUP);
}
if (obj instanceof M3U8PeerTaskListener){
TaskSchedulers.getInstance().register(obj, TaskEnum.M3U8_PEER);
}
if (obj instanceof SubTaskListener){
TaskSchedulers.getInstance().register(obj, TaskEnum.DOWNLOAD_GROUP_SUB);
}
return; return;
} }
Set<Integer> set = ProxyHelper.getInstance().checkProxyType(obj.getClass()); Set<Integer> set = ProxyHelper.getInstance().checkProxyType(obj.getClass());
if (set != null && !set.isEmpty()) { if (set != null && !set.isEmpty()) {
for (Integer type : set) { for (Integer type : set) {
@ -189,7 +205,7 @@ public class DownloadReceiver extends AbsReceiver {
} }
} }
} else { } else {
ALog.w(TAG, "没有Aria的注解方法"); ALog.e(TAG, "没有Aria的注解方法,详情见:https://aria.laoyuyu.me/aria_doc/other/annotaion_invalid.html");
} }
} }
@ -205,24 +221,19 @@ public class DownloadReceiver extends AbsReceiver {
* @see <a href="https://aria.laoyuyu.me/aria_doc/start/any_java.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">module类中销毁</a> * @see <a href="https://aria.laoyuyu.me/aria_doc/start/any_java.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">module类中销毁</a>
*/ */
@Override public void unRegister() { @Override public void unRegister() {
if (needRmListener) { if (isNeedRmListener()) {
unRegisterListener(); unRegisterListener();
} }
AriaManager.getInstance().removeReceiver(OBJ_MAP.get(getKey())); AriaManager.getInstance().removeReceiver(obj);
} }
@Override public String getType() { @Override public ReceiverType getType() {
return ReceiverType.DOWNLOAD; return ReceiverType.DOWNLOAD;
} }
@Override protected void unRegisterListener() { @Override protected void unRegisterListener() {
if (TextUtils.isEmpty(targetName)) {
ALog.e(TAG, "download unRegisterListener target null");
return;
}
Object obj = OBJ_MAP.get(getKey());
if (obj == null) { if (obj == null) {
ALog.e(TAG, String.format("【%s】观察者为空", targetName)); ALog.e(TAG, String.format("unRegister【%s】观察者为空", getTargetName()));
return; return;
} }
Set<Integer> set = ProxyHelper.getInstance().mProxyCache.get(obj.getClass().getName()); Set<Integer> set = ProxyHelper.getInstance().mProxyCache.get(obj.getClass().getName());
@ -230,7 +241,9 @@ public class DownloadReceiver extends AbsReceiver {
for (Integer integer : set) { for (Integer integer : set) {
if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD) { if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD) {
TaskSchedulers.getInstance().unRegister(obj); TaskSchedulers.getInstance().unRegister(obj);
} else if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD_GROUP) { continue;
}
if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD_GROUP) {
TaskSchedulers.getInstance().unRegister(obj); TaskSchedulers.getInstance().unRegister(obj);
} }
} }
@ -243,7 +256,10 @@ public class DownloadReceiver extends AbsReceiver {
* @param taskId 任务实体唯一id * @param taskId 任务实体唯一id
*/ */
public DownloadEntity getDownloadEntity(long taskId) { public DownloadEntity getDownloadEntity(long taskId) {
CheckUtil.checkTaskId(taskId); if (taskId < 0) {
ALog.e(TAG, "taskId错误");
return null;
}
return DbEntity.findFirst(DownloadEntity.class, "rowid=?", String.valueOf(taskId)); return DbEntity.findFirst(DownloadEntity.class, "rowid=?", String.valueOf(taskId));
} }
@ -254,7 +270,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果url错误或查找不到数据则返回null * @return 如果url错误或查找不到数据则返回null
*/ */
public DownloadEntity getFirstDownloadEntity(String downloadUrl) { public DownloadEntity getFirstDownloadEntity(String downloadUrl) {
CheckUtil.checkUrl(downloadUrl); if (!CheckUtil.checkUrl(downloadUrl)) {
return null;
}
return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl); return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl);
} }
@ -264,7 +282,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果url错误或查找不到数据则返回null * @return 如果url错误或查找不到数据则返回null
*/ */
public List<DownloadEntity> getDownloadEntity(String downloadUrl) { public List<DownloadEntity> getDownloadEntity(String downloadUrl) {
CheckUtil.checkUrl(downloadUrl); if (!CheckUtil.checkUrl(downloadUrl)) {
return null;
}
return DbEntity.findDatas(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl); return DbEntity.findDatas(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl);
} }
@ -275,7 +295,9 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果实体不存在返回null * @return 如果实体不存在返回null
*/ */
public DownloadGroupEntity getGroupEntity(long taskId) { public DownloadGroupEntity getGroupEntity(long taskId) {
CheckUtil.checkTaskId(taskId); if (taskId < 0) {
ALog.e(TAG, "任务Id错误");
}
return DbDataHelper.getDGEntity(taskId); return DbDataHelper.getDGEntity(taskId);
} }
@ -286,8 +308,10 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果实体不存在返回null * @return 如果实体不存在返回null
*/ */
public DownloadGroupEntity getGroupEntity(List<String> urls) { public DownloadGroupEntity getGroupEntity(List<String> urls) {
CheckUtil.checkDownloadUrls(urls); if (CheckUtil.checkDownloadUrlsIsEmpty(urls)) {
return DbDataHelper.getDGEntity(CommonUtil.getMd5Code(urls)); return null;
}
return DbDataHelper.getDGEntityByHash(CommonUtil.getMd5Code(urls));
} }
/** /**
@ -297,8 +321,28 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果实体不存在返回null * @return 如果实体不存在返回null
*/ */
public DownloadGroupEntity getFtpDirEntity(String url) { public DownloadGroupEntity getFtpDirEntity(String url) {
CheckUtil.checkUrl(url); if (!CheckUtil.checkUrl(url)) {
return DbDataHelper.getDGEntity(url); return null;
}
return DbDataHelper.getDGEntityByHash(url);
}
/**
* 获取执行中的任务
*
* @return 没有执行中的任务返回null
*/
public List<DownloadEntity> getDRunningTask() {
return DTaskQueue.getInstance().getRunningTask(DownloadEntity.class);
}
/**
* 获取执行中的任务
*
* @return 没有执行中的任务返回null
*/
public List<DownloadGroupEntity> getDGRunningTask() {
return DGroupTaskQueue.getInstance().getRunningTask(DownloadGroupEntity.class);
} }
/** /**
@ -396,15 +440,7 @@ public class DownloadReceiver extends AbsReceiver {
* @return 如果没有任务组列表则返回null * @return 如果没有任务组列表则返回null
*/ */
public List<DownloadGroupEntity> getGroupTaskList() { public List<DownloadGroupEntity> getGroupTaskList() {
List<DGEntityWrapper> wrappers = DbEntity.findRelationData(DGEntityWrapper.class); return getGroupTaskList(1, 10);
if (wrappers == null || wrappers.isEmpty()) {
return null;
}
List<DownloadGroupEntity> entities = new ArrayList<>();
for (DGEntityWrapper wrapper : wrappers) {
entities.add(wrapper.groupEntity);
}
return entities;
} }
/** /**
@ -471,7 +507,7 @@ public class DownloadReceiver extends AbsReceiver {
public void removeAllTask(boolean removeFile) { public void removeAllTask(boolean removeFile) {
final AriaManager ariaManager = AriaManager.getInstance(); final AriaManager ariaManager = AriaManager.getInstance();
CancelAllCmd cancelCmd = CancelAllCmd cancelCmd =
(CancelAllCmd) CommonUtil.createNormalCmd(new DTaskWrapper(null), (CancelAllCmd) CmdHelper.createNormalCmd(new DTaskWrapper(null),
NormalCmdFactory.TASK_CANCEL_ALL, ITask.DOWNLOAD); NormalCmdFactory.TASK_CANCEL_ALL, ITask.DOWNLOAD);
cancelCmd.removeFile = removeFile; cancelCmd.removeFile = removeFile;
EventMsgUtil.getDefault().post(cancelCmd); EventMsgUtil.getDefault().post(cancelCmd);

@ -0,0 +1,26 @@
/*
* 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 com.arialyy.aria.core.scheduler.NormalTaskListenerInterface;
import com.arialyy.aria.core.task.DownloadTask;
/**
* @author ChenFei(chenfei0928@gmail.com)
* Date: 2020-07-07 14:12
*/
public interface DownloadTaskListener extends NormalTaskListenerInterface<DownloadTask> {
}

@ -1,129 +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.download.downloader;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.NormalFileer;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.event.Event;
import com.arialyy.aria.core.event.SpeedEvent;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.File;
import java.io.IOException;
/**
* Created by AriaL on 2017/7/1. 文件下载器
*/
public class Downloader extends NormalFileer<DownloadEntity, DTaskWrapper> {
private String TAG = "Downloader";
public Downloader(IDownloadListener listener, DTaskWrapper taskWrapper) {
super(listener, taskWrapper);
mTempFile = new File(mEntity.getFilePath());
setUpdateInterval(taskWrapper.getConfig().getUpdateInterval());
}
@Override protected boolean handleNewTask() {
if (!mRecord.isBlock) {
if (mTempFile.exists()) {
mTempFile.delete();
}
//CommonUtil.createFile(mTempFile.getPath());
} else {
for (int i = 0; i < mTotalThreadNum; i++) {
File blockFile = new File(String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), i));
if (blockFile.exists()) {
ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i));
blockFile.delete();
}
}
}
BufferedRandomAccessFile file = null;
try {
if (mTotalThreadNum > 1 && !mRecord.isBlock) {
file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192);
//设置文件长度
file.setLength(mEntity.getFileSize());
}
return true;
} catch (IOException e) {
failDownload(new TaskException(TAG,
String.format("下载失败,filePath: %s, url: %s", mEntity.getDownloadPath(),
mEntity.getUrl()), e));
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 如果使用"Content-Disposition"中的文件名需要更新{@link #mTempFile}的路径
*/
void updateTempFile() {
if (!mTempFile.getPath().equals(mEntity.getFilePath())) {
if (!mTempFile.exists()) {
mTempFile = new File(mEntity.getFilePath());
} else {
boolean b = mTempFile.renameTo(new File(mEntity.getDownloadPath()));
ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败"));
}
}
}
@Event
public void setMaxSpeed(SpeedEvent event) {
setMaxSpeed(event.speed);
}
@Override protected void onPostPre() {
super.onPostPre();
((IDownloadListener) mListener).onPostPre(mEntity.getFileSize());
File file = new File(mEntity.getDownloadPath());
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
}
@Override protected AbsThreadTask selectThreadTask(SubThreadConfig<DTaskWrapper> config) {
switch (mTaskWrapper.getRequestType()) {
case ITaskWrapper.D_FTP:
case ITaskWrapper.D_FTP_DIR:
return new FtpThreadTask(config);
case ITaskWrapper.D_HTTP:
return new HttpThreadTask(config);
}
return null;
}
private void failDownload(BaseException e) {
closeTimer();
mListener.onFail(false, e);
}
}

@ -1,61 +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.download.downloader;
import aria.apache.commons.net.ftp.FTPFile;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
/**
* Created by Aria.Lao on 2017/7/25.
* 获取ftp文件信息
*/
class FtpFileInfoThread extends AbsFtpInfoThread<DownloadEntity, DTaskWrapper> {
private final String TAG = "FtpFileInfoThread";
FtpFileInfoThread(DTaskWrapper taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback);
}
@Override protected void handleFile(String remotePath, FTPFile ftpFile) {
super.handleFile(remotePath, ftpFile);
if (!FileUtil.checkSDMemorySpace(mEntity.getFilePath(), ftpFile.getSize())) {
mCallback.onFail(mEntity, new AriaIOException(TAG,
String.format("获取ftp文件信息失败,内存空间不足, filePath: %s", mEntity.getFilePath())),
false);
}
}
@Override protected String getRemotePath() {
return mTaskWrapper.asFtp().getUrlEntity().remotePath;
}
@Override protected void onPreComplete(int code) {
ALog.i(TAG, "FTP下载预处理完成");
super.onPreComplete(code);
if (mSize != mTaskWrapper.getEntity().getFileSize()) {
mTaskWrapper.setNewTask(true);
}
mEntity.setFileSize(mSize);
mCallback.onComplete(mEntity.getUrl(), new CompleteInfo(code, mTaskWrapper));
}
}

@ -1,140 +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.download.downloader;
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.download.DTaskWrapper;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.exception.BaseException;
/**
* Created by lyy on 2015/8/25.
* D_HTTP\FTP单任务下载工具
*/
public class SimpleDownloadUtil implements IUtil {
private String TAG = "SimpleDownloadUtil";
private IDownloadListener mListener;
private Downloader mDownloader;
private DTaskWrapper mTaskWrapper;
private boolean isStop = false, isCancel = false;
public SimpleDownloadUtil(DTaskWrapper wrapper, IDownloadListener downloadListener) {
mTaskWrapper = wrapper;
mListener = downloadListener;
mDownloader = new Downloader(downloadListener, wrapper);
}
@Override public String getKey() {
return mTaskWrapper.getKey();
}
@Override public long getFileSize() {
return mDownloader.getFileSize();
}
/**
* 获取当前下载位置
*/
@Override public long getCurrentLocation() {
return mDownloader.getCurrentLocation();
}
@Override public boolean isRunning() {
return mDownloader.isRunning();
}
/**
* 取消下载
*/
@Override public void cancel() {
isCancel = true;
mDownloader.cancel();
}
/**
* 停止下载
*/
@Override public void stop() {
isStop = true;
mDownloader.stop();
}
/**
* 多线程断点续传下载文件开始下载
*/
@Override public void start() {
if (isStop || isCancel) {
return;
}
mListener.onPre();
// 如果网址没有变,而服务器端端文件改变,以下代码就没有用了
//if (mTaskWrapper.getEntity().getFileSize() <= 1
// || mTaskWrapper.isRefreshInfo()
// || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP
// || mTaskWrapper.getState() == IEntity.STATE_FAIL) {
// new Thread(createInfoThread()).create();
//} else {
// mDownloader.create();
//}
new Thread(createInfoThread()).start();
}
private void failDownload(BaseException e, boolean needRetry) {
if (isStop || isCancel) {
return;
}
mListener.onFail(needRetry, e);
mDownloader.onDestroy();
}
/**
* 通过链接类型创建不同的获取文件信息的线程
*/
private Runnable createInfoThread() {
switch (mTaskWrapper.getRequestType()) {
case ITaskWrapper.D_FTP:
return new FtpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String url, CompleteInfo info) {
mDownloader.updateTempFile();
mDownloader.start();
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
failDownload(e, needRetry);
mDownloader.closeTimer();
}
});
case ITaskWrapper.D_HTTP:
return new HttpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String url, CompleteInfo info) {
mDownloader.updateTempFile();
mDownloader.start();
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
failDownload(e, needRetry);
mDownloader.closeTimer();
}
});
}
return null;
}
}

@ -1,286 +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.download.group;
import android.os.Handler;
import android.os.Looper;
import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.util.ALog;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by AriaL on 2017/6/30.
* 任务组核心逻辑
*/
public abstract class AbsGroupUtil implements IUtil {
private final String TAG = "AbsGroupUtil";
/**
* FTP文件夹
*/
int FTP_DIR = 0xa1;
/**
* D_HTTP 任务组
*/
int HTTP_GROUP = 0xa2;
private long mCurrentLocation = 0;
protected IDGroupListener mListener;
private ScheduledThreadPoolExecutor mTimer;
private long mUpdateInterval;
private boolean isStop = false, isCancel = false;
private Handler mScheduler;
private SimpleSubQueue mSubQueue = SimpleSubQueue.newInstance();
private Map<String, SubDLoadUtil> mExeLoader = new WeakHashMap<>();
private Map<String, DTaskWrapper> mCache = new WeakHashMap<>();
DGTaskWrapper mGTWrapper;
GroupRunState mState;
AbsGroupUtil(IDGroupListener listener, DGTaskWrapper groupWrapper) {
mListener = listener;
mGTWrapper = groupWrapper;
mUpdateInterval = Configuration.getInstance().downloadCfg.getUpdateInterval();
}
/**
* 初始化组合任务状态
*/
void initState() {
mState =
new GroupRunState(mGTWrapper.getKey(), mListener, mGTWrapper.getSubTaskWrapper().size(),
mSubQueue);
mScheduler = new Handler(Looper.getMainLooper(), SimpleSchedulers.newInstance(mState));
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) {
if (wrapper.getEntity().getState() == IEntity.STATE_COMPLETE) {
mState.updateCompleteNum();
mCurrentLocation += wrapper.getEntity().getFileSize();
} else {
mCache.put(wrapper.getKey(), wrapper);
mCurrentLocation += wrapper.getEntity().getCurrentProgress();
}
}
mState.updateProgress(mCurrentLocation);
}
@Override public String getKey() {
return mGTWrapper.getKey();
}
/**
* 获取任务类型
*
* @return {@link #FTP_DIR}{@link #HTTP_GROUP}
*/
abstract int getTaskType();
/**
* 启动子任务下载
*
* @param url 子任务下载地址
*/
public void startSubTask(String url) {
if (!checkSubTask(url, "开始")) return;
if (!mState.isRunning) {
startTimer();
}
SubDLoadUtil d = getDownloader(url);
if (d != null && !d.isRunning()) {
mSubQueue.startTask(d);
}
}
/**
* 停止子任务下载
*
* @param url 子任务下载地址
*/
public void stopSubTask(String url) {
if (!checkSubTask(url, "停止")) return;
SubDLoadUtil d = getDownloader(url);
if (d != null && d.isRunning()) {
mSubQueue.stopTask(d);
}
}
/**
* 检查子任务
*
* @param url 子任务url
* @param type 任务类型
* @return {@code true} 任务可以下载
*/
private boolean checkSubTask(String url, String type) {
DTaskWrapper wrapper = mCache.get(url);
if (wrapper != null) {
if (wrapper.getState() == IEntity.STATE_COMPLETE) {
ALog.w(TAG, "任务【" + url + "】已完成," + type + "失败");
return false;
}
} else {
ALog.w(TAG, "任务组中没有该任务【" + url + "】," + type + "失败");
return false;
}
return true;
}
/**
* 通过地址获取下载器
*
* @param url 子任务下载地址
*/
private SubDLoadUtil getDownloader(String url) {
SubDLoadUtil d = mExeLoader.get(url);
if (d == null) {
return createAndStartSubLoader(mCache.get(url));
}
return d;
}
@Override public long getFileSize() {
return mGTWrapper.getEntity().getFileSize();
}
@Override public long getCurrentLocation() {
return mCurrentLocation;
}
@Override public boolean isRunning() {
return mState != null && mState.isRunning;
}
@Override public void cancel() {
isCancel = true;
closeTimer();
onPreCancel();
mSubQueue.removeAllTask();
mListener.onCancel();
}
/**
* onCancel前的操作
*/
public void onPreCancel() {
}
@Override public void stop() {
isStop = true;
closeTimer();
if (onPreStop()) {
return;
}
mSubQueue.stopAllTask();
}
/**
* onStop前的操作
*
* @return 返回{@code true}直接回调{@link IDGroupListener#onStop(long)}
*/
protected boolean onPreStop() {
return false;
}
@Override public void start() {
if (isStop || isCancel) {
closeTimer();
return;
}
if (onPreStart()) {
startRunningFlow();
}
}
/**
* 处理启动前的检查获取组合任务大小
*
* @return {@code false} 将不再走后续流程任务介绍
*/
protected boolean onPreStart() {
return false;
}
synchronized void closeTimer() {
if (mTimer != null && !mTimer.isShutdown()) {
mTimer.shutdown();
}
}
/**
* 开始进度流程
*/
private void startRunningFlow() {
closeTimer();
mListener.onPostPre(mGTWrapper.getEntity().getFileSize());
if (mCurrentLocation > 0) {
mListener.onResume(mCurrentLocation);
} else {
mListener.onStart(mCurrentLocation);
}
startTimer();
}
private synchronized void startTimer() {
mState.isRunning = true;
mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
if (!mState.isRunning) {
closeTimer();
} else if (mCurrentLocation >= 0) {
long t = 0;
for (DTaskWrapper te : mGTWrapper.getSubTaskWrapper()) {
if (te.getState() == IEntity.STATE_COMPLETE) {
t += te.getEntity().getFileSize();
} else {
t += te.getEntity().getCurrentProgress();
}
}
mCurrentLocation = t;
mState.updateProgress(mCurrentLocation);
mListener.onProgress(t);
}
}
}, 0, mUpdateInterval, TimeUnit.MILLISECONDS);
}
/**
* 创建并启动子任务下载器
*/
SubDLoadUtil createAndStartSubLoader(DTaskWrapper taskWrapper) {
return createAndStartSubLoader(taskWrapper, true);
}
/**
* 创建并启动子任务下载器
*
* @param needGetFileInfo {@code true} 需要获取文件信息{@code false} 不需要获取文件信息
*/
SubDLoadUtil createAndStartSubLoader(DTaskWrapper taskWrapper, boolean needGetFileInfo) {
SubDLoadUtil loader = new SubDLoadUtil(mScheduler, taskWrapper, needGetFileInfo);
mExeLoader.put(loader.getKey(), loader);
mSubQueue.startTask(loader);
return loader;
}
}

@ -1,147 +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.download.group;
import android.os.Handler;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
/**
* 子任务事件监听
*/
class ChildDLoadListener implements IDownloadListener {
private DownloadEntity subEntity;
private int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度
private long lastSaveTime;
private long lastLen;
private Handler schedulers;
private SubDLoadUtil loader;
ChildDLoadListener(Handler schedulers, SubDLoadUtil loader) {
this.loader = loader;
this.schedulers = schedulers;
subEntity = loader.getEntity();
subEntity.setFailNum(0);
lastLen = subEntity.getCurrentProgress();
lastSaveTime = System.currentTimeMillis();
}
@Override public void supportBreakpoint(boolean support) {
}
@Override public void onPre() {
saveData(IEntity.STATE_PRE, subEntity.getCurrentProgress());
}
@Override public void onPostPre(long fileSize) {
subEntity.setFileSize(fileSize);
subEntity.setConvertFileSize(CommonUtil.formatFileSize(fileSize));
saveData(IEntity.STATE_POST_PRE, subEntity.getCurrentProgress());
sendToTarget(ISchedulers.POST_PRE, loader);
}
@Override public void onResume(long resumeLocation) {
lastLen = resumeLocation;
saveData(IEntity.STATE_POST_PRE, subEntity.getCurrentProgress());
sendToTarget(ISchedulers.START, loader);
}
@Override public void onStart(long startLocation) {
lastLen = startLocation;
saveData(IEntity.STATE_POST_PRE, subEntity.getCurrentProgress());
sendToTarget(ISchedulers.START, loader);
}
@Override public void onProgress(long currentLocation) {
long diff = currentLocation - lastLen;
//mCurrentLocation += speed;
subEntity.setCurrentProgress(currentLocation);
handleSpeed(diff);
sendToTarget(ISchedulers.RUNNING, loader);
if (System.currentTimeMillis() - lastSaveTime >= RUN_SAVE_INTERVAL) {
saveData(IEntity.STATE_RUNNING, currentLocation);
lastSaveTime = System.currentTimeMillis();
}
lastLen = currentLocation;
}
@Override public void onStop(long stopLocation) {
handleSpeed(0);
saveData(IEntity.STATE_STOP, stopLocation);
sendToTarget(ISchedulers.STOP, loader);
}
/**
* 组合任务子任务不允许删除
*/
@Deprecated
@Override public void onCancel() {
}
@Override public void onComplete() {
subEntity.setComplete(true);
saveData(IEntity.STATE_COMPLETE, subEntity.getFileSize());
handleSpeed(0);
sendToTarget(ISchedulers.COMPLETE, loader);
}
@Override public void onFail(boolean needRetry, BaseException e) {
subEntity.setFailNum(subEntity.getFailNum() + 1);
saveData(IEntity.STATE_FAIL, subEntity.getCurrentProgress());
handleSpeed(0);
sendToTarget(ISchedulers.FAIL, loader);
}
private void handleSpeed(long speed) {
subEntity.setSpeed(speed);
subEntity.setConvertSpeed(
speed <= 0 ? "" : String.format("%s/s", CommonUtil.formatFileSize(speed)));
subEntity.setPercent((int) (subEntity.getFileSize() <= 0 ? 0
: subEntity.getCurrentProgress() * 100 / subEntity.getFileSize()));
}
private void saveData(int state, long location) {
loader.getWrapper().setState(state);
subEntity.setState(state);
subEntity.setComplete(state == IEntity.STATE_COMPLETE);
if (state == IEntity.STATE_CANCEL) {
subEntity.deleteData();
} else if (state == IEntity.STATE_STOP) {
subEntity.setStopTime(System.currentTimeMillis());
} else if (subEntity.isComplete()) {
subEntity.setCompleteTime(System.currentTimeMillis());
subEntity.setCurrentProgress(subEntity.getFileSize());
} else if (location > 0) {
subEntity.setCurrentProgress(location);
}
}
/**
* 发送状态到子任务调度器{@link SimpleSchedulers}让调度器处理任务调度
*
* @param state {@link ISchedulers}
*/
private void sendToTarget(int state, SubDLoadUtil util) {
schedulers.obtainMessage(state, util).sendToTarget();
}
}

@ -1,188 +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.download.group;
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.common.http.HttpTaskConfig;
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 com.arialyy.aria.util.CommonUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by AriaL on 2017/6/30.
* 任务组下载工具
*/
public class DGroupUtil extends AbsGroupUtil implements IUtil {
private static final String TAG = "DownloadGroupUtil";
private final Object LOCK = new Object();
private ExecutorService mPool = null;
private boolean getLenComplete = false;
private List<DTaskWrapper> mTempWrapper = new ArrayList<>();
public DGroupUtil(IDGroupListener listener, DGTaskWrapper taskWrapper) {
super(listener, taskWrapper);
}
@Override int getTaskType() {
return HTTP_GROUP;
}
@Override public void onPreCancel() {
super.onPreCancel();
}
@Override protected boolean onPreStop() {
// 如果是isUnknownSize()标志,并且获取大小没有完成,则直接回调onStop
if (mPool != null && !getLenComplete) {
ALog.d(TAG, "获取长度未完成的情况下,停止组合任务");
mPool.shutdown();
mListener.onStop(0);
return true;
}
return false;
}
@Override protected boolean onPreStart() {
super.onPreStart();
initState();
if (mState.getCompleteNum() == mState.getSubSize()) {
mListener.onComplete();
} else {
// 处理组合任务大小未知的情况
if (mGTWrapper.isUnknownSize() && mGTWrapper.getEntity().getFileSize() < 1) {
mPool = Executors.newCachedThreadPool();
getGroupSize();
try {
synchronized (LOCK) {
LOCK.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return getLenComplete;
} else {
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) {
if (wrapper.getState() != IEntity.STATE_COMPLETE) {
createAndStartSubLoader(wrapper);
}
}
}
}
return true;
}
/**
* 获取组合任务大小使用该方式获取到的组合任务大小子任务不需要再重新获取文件大小
*/
private void getGroupSize() {
new Thread(new Runnable() {
int count;
int failCount;
@Override public void run() {
for (DTaskWrapper dTaskWrapper : mGTWrapper.getSubTaskWrapper()) {
cloneHeader(dTaskWrapper);
mPool.submit(new HttpFileInfoThread(dTaskWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String url, CompleteInfo info) {
if (!mGTWrapper.isUnknownSize()) {
createAndStartSubLoader((DTaskWrapper) info.wrapper, false);
} else {
mTempWrapper.add((DTaskWrapper) info.wrapper);
}
count++;
checkGetSizeComplete(count, failCount);
ALog.d(TAG, "获取子任务信息完成");
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
ALog.e(TAG, String.format("获取文件信息失败,url:%s", ((DownloadEntity) entity).getUrl()));
count++;
failCount++;
mListener.onSubFail((DownloadEntity) entity, new AriaIOException(TAG,
String.format("子任务获取文件长度失败,url:%s", ((DownloadEntity) entity).getUrl())));
checkGetSizeComplete(count, failCount);
mState.countFailNum(entity.getKey());
}
}));
}
}
}).start();
}
/**
* 检查组合任务大小是否获取完成获取完成后取消阻塞并设置组合任务大小
*/
private void checkGetSizeComplete(int count, int failCount) {
if (failCount == mGTWrapper.getSubTaskWrapper().size()) {
mState.isRunning = false;
mListener.onFail(false, new AriaIOException(TAG, "获取子任务长度失败"));
notifyLock();
return;
}
if (count == mGTWrapper.getSubTaskWrapper().size()) {
long size = 0;
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) {
size += wrapper.getEntity().getFileSize();
}
mGTWrapper.getEntity().setConvertFileSize(CommonUtil.formatFileSize(size));
mGTWrapper.getEntity().setFileSize(size);
mGTWrapper.getEntity().update();
getLenComplete = true;
ALog.d(TAG, String.format("获取组合任务长度完成,组合任务总长度:%s,失败的只任务数:%s", size, failCount));
// 未知大小的组合任务,延迟下载
if (mGTWrapper.isUnknownSize()) {
for (DTaskWrapper wrapper : mTempWrapper) {
createAndStartSubLoader(wrapper, false);
}
}
notifyLock();
}
}
private void notifyLock() {
synchronized (LOCK) {
LOCK.notifyAll();
}
}
/**
* 子任务使用父包裹器的属性
*/
private void cloneHeader(DTaskWrapper taskWrapper) {
HttpTaskConfig groupDelegate = mGTWrapper.asHttp();
HttpTaskConfig subDelegate = taskWrapper.asHttp();
// 设置属性
subDelegate.setFileLenAdapter(groupDelegate.getFileLenAdapter());
subDelegate.setRequestEnum(groupDelegate.getRequestEnum());
subDelegate.setHeaders(groupDelegate.getHeaders());
subDelegate.setProxy(groupDelegate.getProxy());
subDelegate.setParams(groupDelegate.getParams());
}
}

@ -1,109 +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.download.group;
import android.net.Uri;
import android.text.TextUtils;
import com.arialyy.aria.core.common.ftp.FtpUrlEntity;
import com.arialyy.aria.core.common.CompleteInfo;
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;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by Aria.Lao on 2017/7/27.
* ftp文件夹下载工具
*/
public class FtpDirDownloadUtil extends AbsGroupUtil {
private String TAG = "FtpDirDownloadUtil";
private ReentrantLock LOCK = new ReentrantLock();
private Condition condition = LOCK.newCondition();
public FtpDirDownloadUtil(IDGroupListener listener, DGTaskWrapper taskEntity) {
super(listener, taskEntity);
}
@Override int getTaskType() {
return FTP_DIR;
}
@Override protected boolean onPreStart() {
super.onPreStart();
if (mGTWrapper.getEntity().getFileSize() > 1) {
startDownload(true);
} else {
FtpDirInfoThread infoThread = new FtpDirInfoThread(mGTWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String url, CompleteInfo info) {
if (info.code >= 200 && info.code < 300) {
startDownload(false);
}
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
mListener.onFail(needRetry, e);
}
});
new Thread(infoThread).start();
try {
LOCK.lock();
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
return true;
}
/**
* @param needCloneInfo 第一次下载信息已经在{@link FtpDirInfoThread}中clone了
*/
private void startDownload(boolean needCloneInfo) {
try {
LOCK.lock();
condition.signalAll();
} finally {
LOCK.unlock();
}
initState();
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) {
if (needCloneInfo) {
cloneInfo(wrapper);
}
if (wrapper.getState() != IEntity.STATE_COMPLETE) {
createAndStartSubLoader(wrapper);
}
}
}
private void cloneInfo(DTaskWrapper subWrapper) {
FtpUrlEntity urlEntity = mGTWrapper.asFtp().getUrlEntity().clone();
Uri uri = Uri.parse(subWrapper.getEntity().getUrl());
String remotePath = uri.getPath();
urlEntity.remotePath = TextUtils.isEmpty(remotePath) ? "/" : remotePath;
subWrapper.asFtp().setUrlEntity(urlEntity);
subWrapper.asFtp().setCharSet(mGTWrapper.asFtp().getCharSet());
subWrapper.asFtp().setProxy(mGTWrapper.asFtp().getProxy());
}
}

@ -1,104 +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.download.group;
import aria.apache.commons.net.ftp.FTPFile;
import com.arialyy.aria.core.common.ftp.FtpUrlEntity;
import com.arialyy.aria.core.common.CompleteInfo;
import com.arialyy.aria.core.common.OnFileInfoCallback;
import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread;
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.DownloadGroupEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
import java.nio.charset.Charset;
import java.util.ArrayList;
/**
* Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息
*/
class FtpDirInfoThread extends AbsFtpInfoThread<DownloadGroupEntity, DGTaskWrapper> {
FtpDirInfoThread(DGTaskWrapper taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback);
}
@Override protected String getRemotePath() {
return mTaskWrapper.asFtp().getUrlEntity().remotePath;
}
@Override protected void handleFile(String remotePath, FTPFile ftpFile) {
super.handleFile(remotePath, ftpFile);
addEntity(remotePath, ftpFile);
}
@Override protected void onPreComplete(int code) {
super.onPreComplete(code);
mEntity.setFileSize(mSize);
mCallback.onComplete(mEntity.getKey(), new CompleteInfo(code, mTaskWrapper));
}
/**
* FTP文件夹的子任务实体 在这生成
*/
private void addEntity(String remotePath, FTPFile ftpFile) {
final FtpUrlEntity urlEntity = mTaskWrapper.asFtp().getUrlEntity().clone();
DownloadEntity entity = new DownloadEntity();
entity.setUrl(
urlEntity.scheme + "://" + urlEntity.hostName + ":" + urlEntity.port + "/" + remotePath);
entity.setFilePath(mEntity.getDirPath() + "/" + remotePath);
int lastIndex = remotePath.lastIndexOf("/");
String fileName = lastIndex < 0 ? CommonUtil.keyToHashKey(remotePath)
: remotePath.substring(lastIndex + 1);
entity.setFileName(
new String(fileName.getBytes(), Charset.forName(mTaskWrapper.asHttp().getCharSet())));
entity.setGroupHash(mEntity.getGroupHash());
entity.setGroupChild(true);
entity.setConvertFileSize(CommonUtil.formatFileSize(ftpFile.getSize()));
entity.setFileSize(ftpFile.getSize());
entity.insert();
DTaskWrapper subWrapper = new DTaskWrapper(entity);
subWrapper.setGroupTask(true);
subWrapper.setGroupHash(mEntity.getGroupHash());
subWrapper.setRequestType(AbsTaskWrapper.D_FTP);
urlEntity.url = entity.getUrl();
urlEntity.remotePath = remotePath;
cloneInfo(subWrapper, urlEntity);
if (mEntity.getUrls() == null) {
mEntity.setUrls(new ArrayList<String>());
}
mEntity.getSubEntities().add(entity);
mTaskWrapper.getSubTaskWrapper().add(subWrapper);
}
private void cloneInfo(DTaskWrapper subWrapper, FtpUrlEntity urlEntity) {
subWrapper.asFtp().setUrlEntity(urlEntity);
subWrapper.asFtp().setCharSet(mTaskWrapper.asFtp().getCharSet());
subWrapper.asFtp().setProxy(mTaskWrapper.asFtp().getProxy());
}
@Override protected void failDownload(BaseException e, boolean needRetry) {
super.failDownload(e, needRetry);
RecordUtil.delGroupTaskRecord(mTaskWrapper.getEntity(), true, true);
}
}

@ -1,65 +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.download.group;
import com.arialyy.aria.core.common.AbsNormalTarget;
import java.util.List;
/**
* @Author aria
* @Date 2019-09-05
*/
public class GroupTargetFactory {
public static volatile GroupTargetFactory INSTANCE;
private GroupTargetFactory() {
}
public static GroupTargetFactory getInstance() {
if (INSTANCE == null) {
synchronized (GroupTargetFactory.class) {
if (INSTANCE == null) {
INSTANCE = new GroupTargetFactory();
}
}
}
return INSTANCE;
}
public <T extends AbsNormalTarget> T generateNormalTarget(Class<T> clazz, long taskId,
String targetName) {
T target = null;
if (clazz == GroupNormalTarget.class) {
target = (T) new GroupNormalTarget(taskId, targetName);
} else if (clazz == FtpDirNormalTarget.class) {
target = (T) new FtpDirNormalTarget(taskId, targetName);
}
return target;
}
public FtpDirBuilderTarget generateDirBuilderTarget(String url, String targetName) {
return new FtpDirBuilderTarget(url, targetName);
}
public GroupBuilderTarget generateGroupBuilderTarget(List<String> urls, String targetName) {
return new GroupBuilderTarget(urls, targetName);
}
}

@ -1,193 +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.download.group;
import android.os.Message;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.config.Configuration;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.NetUtils;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 组合任务子任务调度器用于调度任务的开始停止失败完成等情况
* 该调度器生命周期和{@link AbsGroupUtil}生命周期一致
*/
class SimpleSchedulers implements ISchedulers {
private static final String TAG = "SimpleSchedulers";
private SimpleSubQueue mQueue;
private GroupRunState mGState;
private SimpleSchedulers(GroupRunState state) {
mQueue = state.queue;
mGState = state;
}
static SimpleSchedulers newInstance(GroupRunState state) {
return new SimpleSchedulers(state);
}
@Override public boolean handleMessage(Message msg) {
SubDLoadUtil loader = (SubDLoadUtil) msg.obj;
switch (msg.what) {
case RUNNING:
mGState.listener.onSubRunning(loader.getEntity());
break;
case PRE:
mGState.listener.onSubPre(loader.getEntity());
mGState.updateCount(loader.getKey());
break;
case START:
mGState.listener.onSubStart(loader.getEntity());
break;
case STOP:
handleStop(loader);
break;
case COMPLETE:
handleComplete(loader);
break;
case FAIL:
handleFail(loader);
break;
}
return true;
}
/**
* 处理子任务失败的情况
* 1子任务失败次数大于等于配置的重试次数才能认为子任务停止
* 2stopNum + failNum + completeNum + cacheNum == subSize则认为组合任务停止
* 3failNum == subSize只有全部的子任务都失败了才能任务组合任务失败
*/
private synchronized void handleFail(final SubDLoadUtil loaderUtil) {
Configuration config = Configuration.getInstance();
long interval = config.dGroupCfg.getSubReTryInterval();
int num = config.dGroupCfg.getSubReTryNum();
boolean isNotNetRetry = config.appCfg.isNotNetRetry();
final int reTryNum = num;
if ((!NetUtils.isConnected(AriaManager.getInstance().getAPP()) && !isNotNetRetry)
|| loaderUtil.getDownloader() == null // 如果获取不到文件信息,loader为空
|| loaderUtil.getEntity().getFailNum() > reTryNum) {
mQueue.removeTaskFromExecQ(loaderUtil);
mGState.listener.onSubFail(loaderUtil.getEntity(), new TaskException(TAG,
String.format("任务组子任务【%s】下载失败,下载地址【%s】", loaderUtil.getEntity().getFileName(),
loaderUtil.getEntity().getUrl())));
mGState.countFailNum(loaderUtil.getKey());
if (mGState.getFailNum() == mGState.getSubSize()
|| mGState.getStopNum() + mGState.getFailNum() + mGState.getCompleteNum()
== mGState.getSubSize()) {
mQueue.clear();
mGState.isRunning = false;
mGState.listener.onFail(false, new TaskException(TAG,
String.format("任务组【%s】下载失败", mGState.getGroupHash())));
} else {
startNext();
}
return;
}
// 如果获取不到文件信息,loader为空
final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
timer.schedule(new Runnable() {
@Override public void run() {
AbsEntity entity = loaderUtil.getEntity();
if (entity.getFailNum() <= reTryNum) {
ALog.d(TAG, String.format("任务【%s】开始重试", loaderUtil.getEntity().getFileName()));
loaderUtil.reStart();
} else {
startNext();
}
}
}, interval, TimeUnit.MILLISECONDS);
}
/**
* 处理子任务停止的情况
* 1所有的子任务已经停止则认为组合任务停止
* 2completeNum + failNum + stopNum = subSize则认为组合任务停止
*/
private synchronized void handleStop(SubDLoadUtil loader) {
mGState.listener.onSubStop(loader.getEntity());
mGState.countStopNum(loader.getKey());
if (mGState.getStopNum() == mGState.getSubSize()
|| mGState.getStopNum()
+ mGState.getCompleteNum()
+ mGState.getFailNum()
+ mQueue.getCacheSize()
== mGState.getSubSize()) {
mQueue.clear();
mGState.isRunning = false;
mGState.listener.onStop(mGState.getProgress());
} else {
startNext();
}
}
/**
* 处理子任务完成的情况有以下三种情况
* 1已经没有缓存的子任务并且停止的子任务是数{@link GroupRunState#getStopNum()} ()}为0失败的子任数{@link
* GroupRunState#getFailNum()}为0则认为组合任务已经完成
* 2已经没有缓存的子任务并且停止的子任务是数{@link GroupRunState#getCompleteNum()}不为0或者失败的子任数{@link
* GroupRunState#getFailNum()}不为0则认为组合任务被停止
* 3只有有缓存的子任务则任务组合任务没有完成
*/
private synchronized void handleComplete(SubDLoadUtil loader) {
ALog.d(TAG, String.format("子任务【%s】完成", loader.getEntity().getFileName()));
ThreadTaskManager.getInstance().removeTaskThread(loader.getKey());
mGState.listener.onSubComplete(loader.getEntity());
mQueue.removeTaskFromExecQ(loader);
mGState.updateCompleteNum();
ALog.d(TAG, String.format("总任务数:%s,完成的任务数:%s,失败的任务数:%s,停止的任务数:%s", mGState.getSubSize(),
mGState.getCompleteNum(), mGState.getFailNum(), mGState.getStopNum()));
if (mGState.getCompleteNum() + mGState.getFailNum() + mGState.getStopNum()
== mGState.getSubSize()) {
if (mGState.getStopNum() == 0 && mGState.getFailNum() == 0) {
mGState.listener.onComplete();
} else {
mGState.listener.onStop(mGState.getProgress());
}
mQueue.clear();
mGState.isRunning = false;
} else {
startNext();
}
}
/**
* 如果有等待中的任务则启动下一任务
*/
private void startNext() {
if (mQueue.isStopAll()) {
return;
}
SubDLoadUtil next = mQueue.getNextTask();
if (next != null) {
ALog.d(TAG, String.format("启动任务:%s", next.getEntity().getFileName()));
mQueue.startTask(next);
} else {
ALog.i(TAG, "没有下一子任务");
}
}
}

@ -1,133 +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.download.group;
import android.os.Handler;
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.download.DTaskWrapper;
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.ITaskWrapper;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
/**
* 子任务下载器负责创建{@link Downloader}
*/
class SubDLoadUtil implements IUtil {
private final String TAG = "SubDownloadLoader";
private Downloader mDownloader;
private DTaskWrapper mWrapper;
private Handler mSchedulers;
private ChildDLoadListener mListener;
private boolean needGetInfo;
/**
* @param schedulers 调度器
* @param needGetInfo {@code true} 需要获取文件信息{@code false} 不需要获取文件信息
*/
SubDLoadUtil(Handler schedulers, DTaskWrapper taskWrapper, boolean needGetInfo) {
mWrapper = taskWrapper;
mSchedulers = schedulers;
this.needGetInfo = needGetInfo;
mListener = new ChildDLoadListener(mSchedulers, SubDLoadUtil.this);
}
@Override public String getKey() {
return mWrapper.getKey();
}
public DTaskWrapper getWrapper() {
return mWrapper;
}
public DownloadEntity getEntity() {
return mWrapper.getEntity();
}
/**
* 重新开始任务
*/
void reStart() {
if (mDownloader != null) {
mDownloader.retryTask();
}
}
public Downloader getDownloader() {
return mDownloader;
}
@Override public long getFileSize() {
return mDownloader == null ? -1 : mDownloader.getFileSize();
}
@Override public long getCurrentLocation() {
return mDownloader == null ? -1 : mDownloader.getCurrentLocation();
}
@Override public boolean isRunning() {
return mDownloader != null && mDownloader.isRunning();
}
@Override public void cancel() {
if (mDownloader != null && isRunning()) {
mDownloader.cancel();
} else {
mSchedulers.obtainMessage(ISchedulers.CANCEL, this).sendToTarget();
}
}
@Override public void stop() {
if (mDownloader != null && isRunning()) {
mDownloader.stop();
} else {
mSchedulers.obtainMessage(ISchedulers.STOP, this).sendToTarget();
}
}
@Override public void start() {
if (mWrapper.getRequestType() == ITaskWrapper.D_HTTP) {
if (needGetInfo) {
new Thread(new HttpFileInfoThread(mWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String url, CompleteInfo info) {
mDownloader = new Downloader(mListener, mWrapper);
mDownloader.start();
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
mSchedulers.obtainMessage(ISchedulers.FAIL, SubDLoadUtil.this).sendToTarget();
}
})).start();
} else {
mDownloader = new Downloader(mListener, mWrapper);
mDownloader.start();
}
} else if (mWrapper.getRequestType() == ITaskWrapper.D_FTP) {
mDownloader = new Downloader(mListener, mWrapper);
mDownloader.start();
} else {
ALog.w(TAG, String.format("不识别的类型,requestType:%s", mWrapper.getRequestType()));
}
}
}

@ -1,118 +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.download.m3u8;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
/**
* m3u8 委托
*/
public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
private DTaskWrapper mTaskWrapper;
public M3U8Delegate(TARGET target, AbsTaskWrapper wrapper) {
super(target, wrapper);
mTaskWrapper = (DTaskWrapper) getTaskWrapper();
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_VOD);
}
/**
* 生成m3u8索引文件
* 注意创建索引文件{@link #merge(boolean)}方法设置与否都不再合并文件
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8Delegate<TARGET> generateIndexFile() {
mTaskWrapper.asM3U8().setGenerateIndexFileTemp(true);
return this;
}
/**
* 是否合并ts文件默认合并ts
*
* @param merge {@code true}合并所有ts文件为一个
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8Delegate<TARGET> merge(boolean merge) {
mTaskWrapper.asM3U8().setMergeFile(merge);
return this;
}
/**
* 如果你希望使用自行处理ts文件的合并可以使用{@link ITsMergeHandler}处理ts文件的合并
* 需要注意的是只有{@link #merge(boolean)}设置合并ts文件该方法才会生效
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8Delegate<TARGET> setMergeHandler(ITsMergeHandler handler) {
mTaskWrapper.asM3U8().setMergeHandler(handler);
return this;
}
/**
* M3U8 ts 文件url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param converter {@link IVodTsUrlConverter}
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8Delegate<TARGET> setTsUrlConvert(IVodTsUrlConverter converter) {
mTaskWrapper.asM3U8().setVodUrlConverter(converter);
return this;
}
/**
* 选择需要下载的码率默认下载的码率
*
* @param bandWidth 指定的码率
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8Delegate<TARGET> setBandWidth(int bandWidth) {
mTaskWrapper.asM3U8().setBandWidth(bandWidth);
return this;
}
/**
* M3U8 bandWidth 码率url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param converter {@link IBandWidthUrlConverter}
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8Delegate<TARGET> setBandWidthUrlConverter(IBandWidthUrlConverter converter) {
mTaskWrapper.asM3U8().setBandWidthUrlConverter(converter);
return this;
}
/**
* 处理点播文件的下载参数
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8VodDelegate<TARGET> asVod() {
return new M3U8VodDelegate<>(mTarget, mTaskWrapper);
}
/**
* 处理直播类的下载
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8LiveDelegate<TARGET> asLive() {
return new M3U8LiveDelegate<>(mTarget, mTaskWrapper);
}
}

@ -1,261 +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.download.m3u8;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.arialyy.aria.core.common.IThreadState;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.common.TaskRecord;
import com.arialyy.aria.core.common.ThreadRecord;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.IdGenerator;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* M3U8点播文件下载器
*/
public class M3U8LiveLoader extends BaseM3U8Loader {
/**
* 最大执行数
*/
private static final int EXEC_MAX_NUM = 4;
private Handler mStateHandler;
private ArrayBlockingQueue<Long> mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM);
private LiveStateManager mManager;
private ReentrantLock LOCK = new ReentrantLock();
private Condition mCondition = LOCK.newCondition();
private LinkedBlockingQueue<String> mPeerQueue = new LinkedBlockingQueue<>();
M3U8LiveLoader(IEventListener listener, DTaskWrapper wrapper) {
super(listener, wrapper);
}
@Override protected IThreadState getStateManager(Looper looper) {
mManager = new LiveStateManager(looper, mListener);
mStateHandler = new Handler(looper, mManager);
return mManager;
}
void offerPeer(String peerUrl) {
mPeerQueue.offer(peerUrl);
}
@Override protected void handleTask() {
new Thread(new Runnable() {
@Override public void run() {
String cacheDir = getCacheDir();
int index = 0;
while (!isBreak()) {
try {
LOCK.lock();
while (mFlagQueue.size() < EXEC_MAX_NUM) {
String url = mPeerQueue.poll();
if (url == null) {
break;
}
M3U8ThreadTask task = createThreadTask(cacheDir, index, url);
getTaskList().put(index, task);
mFlagQueue.offer(startThreadTask(task));
index++;
}
if (mFlagQueue.size() > 0) {
mCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
}
}).start();
}
private void notifyLock() {
try {
LOCK.lock();
long id = mFlagQueue.take();
ALog.d(TAG, String.format("线程【%s】完成", id));
mCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
/**
* 启动线程任务
*
* @return 线程唯一id标志
*/
private long startThreadTask(M3U8ThreadTask task) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task);
return IdGenerator.getInstance().nextId();
}
/**
* 配置config
*/
private M3U8ThreadTask createThreadTask(String cacheDir, int indexId, String tsUrl) {
ThreadRecord record = new ThreadRecord();
record.taskKey = mRecord.filePath;
record.isComplete = false;
record.tsUrl = tsUrl;
record.threadType = TaskRecord.TYPE_M3U8_LIVE;
record.threadId = indexId;
SubThreadConfig<DTaskWrapper> config = new SubThreadConfig<>();
config.url = tsUrl;
config.tempFile = new File(getTsFilePath(cacheDir, indexId));
config.isBlock = mRecord.isBlock;
config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
config.taskWrapper = mTaskWrapper;
config.record = record;
config.stateHandler = mStateHandler;
if (!config.tempFile.exists()) {
FileUtil.createFile(config.tempFile.getPath());
}
return new M3U8ThreadTask(config);
}
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
public boolean mergeFile() {
if (getEntity().getM3U8Entity().isGenerateIndexFile()) {
return generateIndexFile();
}
ITsMergeHandler mergeHandler = mTaskWrapper.asM3U8().getMergeHandler();
String cacheDir = getCacheDir();
List<String> partPath = new ArrayList<>();
String[] tsNames = new File(cacheDir).list(new FilenameFilter() {
@Override public boolean accept(File dir, String name) {
return name.endsWith(".ts");
}
});
for (String tsName : tsNames) {
partPath.add(cacheDir + "/" + tsName);
}
boolean isSuccess;
if (mergeHandler != null) {
isSuccess = mergeHandler.merge(getEntity().getM3U8Entity(), partPath);
} else {
isSuccess = FileUtil.mergeFile(mEntity.getFilePath(), partPath);
}
if (isSuccess) {
// 合并成功,删除缓存文件
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File cDir = new File(cacheDir);
if (cDir.exists()) {
cDir.delete();
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
/**
* M3U8线程状态管理直播不处理停止状态删除失败的状态
*/
private class LiveStateManager implements IThreadState {
private final String TAG = "M3U8ThreadStateManager";
/**
* 任务状态回调
*/
private IEventListener mListener;
private long mProgress; //当前总进度
private Looper mLooper;
/**
* @param listener 任务事件
*/
LiveStateManager(Looper looper, IEventListener listener) {
mLooper = looper;
mListener = listener;
}
/**
* 退出looper循环
*/
private void quitLooper() {
ALog.d(TAG, "quitLooper");
mLooper.quit();
}
@Override public boolean handleMessage(Message msg) {
switch (msg.what) {
case STATE_STOP:
if (isBreak()) {
ALog.d(TAG, "任务停止");
quitLooper();
}
break;
case STATE_CANCEL:
if (isBreak()) {
ALog.d(TAG, "任务取消");
quitLooper();
}
break;
case STATE_COMPLETE:
notifyLock();
break;
case STATE_RUNNING:
mProgress += (long) msg.obj;
break;
}
return false;
}
@Override public boolean isFail() {
return false;
}
@Override public boolean isComplete() {
return false;
}
@Override public long getCurrentProgress() {
return mProgress;
}
}
}

@ -15,48 +15,46 @@
*/ */
package com.arialyy.aria.core.download.m3u8; package com.arialyy.aria.core.download.m3u8;
import androidx.annotation.CheckResult; import com.arialyy.aria.core.processor.ILiveTsUrlConverter;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
/** /**
* m3u8直播参数设置 * m3u8直播参数设置
*/ */
public class M3U8LiveDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> { public class M3U8LiveOption extends M3U8Option<M3U8LiveOption> {
M3U8LiveDelegate(TARGET target, AbsTaskWrapper wrapper) { private ILiveTsUrlConverter liveTsUrlConverter;
super(target, wrapper); private long liveUpdateInterval;
getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE);
public M3U8LiveOption() {
super();
} }
/** /**
* M3U8 ts 文件url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的 * M3U8 ts 文件url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址 * 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
* *
* @param converter {@link ILiveTsUrlConverter} * @param liveTsUrlConverter {@link ILiveTsUrlConverter}
*/ */
@CheckResult(suggest = Suggest.TO_CONTROLLER) public M3U8LiveOption setLiveTsUrlConvert(ILiveTsUrlConverter liveTsUrlConverter) {
public M3U8LiveDelegate<TARGET> setLiveTsUrlConvert(ILiveTsUrlConverter converter) { CheckUtil.checkMemberClass(liveTsUrlConverter.getClass());
((DTaskWrapper) getTaskWrapper()).asM3U8().setLiveTsUrlConverter(converter); this.liveTsUrlConverter = liveTsUrlConverter;
return this; return this;
} }
/** /**
* 设置直播的m3u8文件更新间隔默认10000微秒 * 设置直播的m3u8文件更新间隔默认10000微秒
* *
* @param interval 更新间隔单位微秒 * @param liveUpdateInterval 更新间隔单位微秒
*/ */
@CheckResult(suggest = Suggest.TO_CONTROLLER) public M3U8LiveOption setM3U8FileUpdateInterval(long liveUpdateInterval) {
public M3U8LiveDelegate<TARGET> setM3U8FileUpdateInterval(long interval) { if (liveUpdateInterval <= 1) {
if (interval <= 1) {
ALog.e(TAG, "间隔时间错误"); ALog.e(TAG, "间隔时间错误");
return this; return this;
} }
((DTaskWrapper) getTaskWrapper()).asM3U8().setLiveUpdateInterval(interval);
this.liveUpdateInterval = liveUpdateInterval;
return this; return this;
} }
} }

@ -1,180 +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.download.m3u8;
import android.text.TextUtils;
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.download.DTaskWrapper;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.M3U8Exception;
import com.arialyy.aria.util.ALog;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* M3U8直播文件下载工具对于直播来说需要定时更新m3u8文件
* 工作流程
* 1持续获取切片信息直到调用停止|取消才停止获取切片信息
* 2完成所有分片下载后合并ts文件
* 3删除该隐藏文件夹
* 4对于直播来说是没有停止的停止就代表完成
* 5不处理直播切片下载失败的状态
*/
public class M3U8LiveUtil implements IUtil {
private final String TAG = "M3U8LiveDownloadUtil";
private DTaskWrapper mWrapper;
private IDownloadListener mListener;
private boolean isStop = false, isCancel = false;
private M3U8LiveLoader mLoader;
private M3U8InfoThread mInfoThread;
private ScheduledThreadPoolExecutor mTimer;
private ExecutorService mInfoPool = Executors.newCachedThreadPool();
private List<String> mPeerUrls = new ArrayList<>();
public M3U8LiveUtil(DTaskWrapper wrapper, IDownloadListener listener) {
mWrapper = wrapper;
mListener = listener;
mLoader = new M3U8LiveLoader(mListener, mWrapper);
}
@Override public String getKey() {
return mWrapper.getKey();
}
@Override public long getFileSize() {
return 0;
}
@Override public long getCurrentLocation() {
return 0;
}
@Override public boolean isRunning() {
return mLoader.isRunning();
}
@Override public void cancel() {
isCancel = true;
mLoader.cancel();
if (mInfoThread != null) {
mInfoThread.setStop(true);
}
}
/**
* 对于直播来说是没有停止的停止就代表完成
*/
@Override public void stop() {
isStop = true;
mLoader.stop();
handleComplete();
}
private void handleComplete() {
if (mInfoThread != null) {
mInfoThread.setStop(true);
closeTimer();
if (mWrapper.asM3U8().isMergeFile()) {
if (mLoader.mergeFile()) {
mListener.onComplete();
} else {
mListener.onFail(false, new M3U8Exception(TAG, "合并文件失败"));
}
} else {
mListener.onComplete();
}
}
}
@Override public void start() {
if (isStop || isCancel) {
return;
}
mListener.onPre();
getLiveInfo();
mLoader.start();
startTimer();
}
private void startTimer() {
mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
mInfoThread = (M3U8InfoThread) getLiveInfo();
mInfoPool.execute(mInfoThread);
}
}, 0, mWrapper.asM3U8().getLiveUpdateInterval(), TimeUnit.MILLISECONDS);
}
private void closeTimer() {
if (mTimer != null && !mTimer.isShutdown()) {
mTimer.shutdown();
}
}
/**
* 获取直播文件信息
*/
private Runnable getLiveInfo() {
M3U8InfoThread infoThread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String key, CompleteInfo info) {
ALog.d(TAG, "更新直播的m3u8文件");
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
failDownload(e, needRetry);
}
});
infoThread.setOnGetPeerCallback(new M3U8InfoThread.OnGetLivePeerCallback() {
@Override public void onGetPeer(String url) {
if (mPeerUrls.contains(url)) {
return;
}
mPeerUrls.add(url);
ILiveTsUrlConverter converter = mWrapper.asM3U8().getLiveTsUrlConverter();
if (converter != null) {
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) {
url = converter.convert(mWrapper.getEntity().getUrl(), url);
} else {
url = converter.convert(mWrapper.asM3U8().getBandWidthUrl(), url);
}
}
if (TextUtils.isEmpty(url) || !url.startsWith("http")) {
failDownload(new M3U8Exception(TAG, String.format("ts地址错误,url:%s", url)), false);
return;
}
mLoader.offerPeer(url);
}
});
return infoThread;
}
private void failDownload(BaseException e, boolean needRetry) {
if (isStop || isCancel) {
return;
}
handleComplete();
mListener.onFail(needRetry, e);
}
}

@ -0,0 +1,141 @@
/*
* 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.m3u8;
import android.text.TextUtils;
import com.arialyy.aria.core.common.BaseOption;
import com.arialyy.aria.core.processor.IBandWidthUrlConverter;
import com.arialyy.aria.core.processor.IKeyUrlConverter;
import com.arialyy.aria.core.processor.ITsMergeHandler;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.ComponentUtil;
/**
* m3u8任务设置
*/
public class M3U8Option<OP extends M3U8Option> extends BaseOption {
private boolean generateIndexFile = false;
private boolean mergeFile = true;
private int bandWidth;
private ITsMergeHandler mergeHandler;
private IBandWidthUrlConverter bandWidthUrlConverter;
private IKeyUrlConverter keyUrlConverter;
private boolean ignoreFailureTs = false;
private String keyPath;
private boolean useDefConvert = true;
M3U8Option() {
super();
ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_M3U8);
}
/**
* 设置使用默认的码率转换器和TS转换器默认打开
* @param useDefConvert true 使用默认的转换器false 关闭默认的转换器
*/
public OP setUseDefConvert(boolean useDefConvert) {
this.useDefConvert = useDefConvert;
ALog.d(TAG, "使用默认的码率转换器和TS转换器,如果无法下载,请参考:https://github.com/AriaLyy/Aria/issues/597 定制转换器");
return (OP) this;
}
/**
* 设置密钥文件的保存路径
*
* @param keyPath 密钥文件的保存路径
*/
public OP setKeyPath(String keyPath) {
if (TextUtils.isEmpty(keyPath)) {
ALog.e(TAG, "密钥文件保存路径为空");
return (OP) this;
}
this.keyPath = keyPath;
return (OP) this;
}
/**
* 忽略下载失败的ts切片即使有失败的切片下载完成后也要合并所有切片并进入complete回调
*/
public OP ignoreFailureTs() {
this.ignoreFailureTs = true;
return (OP) this;
}
/**
* 生成m3u8索引文件
* 注意创建索引文件{@link #merge(boolean)}方法设置与否都不再合并文件
* 如果是直播文件下载创建索引文件的操作将导致只能同时下载一个切片
*/
public OP generateIndexFile() {
this.generateIndexFile = true;
return (OP) this;
}
/**
* 下载完成后将所有的切片合并为一个文件
*
* @param mergeFile {@code true}合并所有ts文件为一个
*/
public OP merge(boolean mergeFile) {
this.mergeFile = mergeFile;
return (OP) this;
}
/**
* 如果你希望使用自行处理ts文件的合并可以使用{@link ITsMergeHandler}处理ts文件的合并
* 需要注意的是只有{@link #merge(boolean)}设置合并ts文件该方法才会生效
*/
public OP setMergeHandler(ITsMergeHandler mergeHandler) {
CheckUtil.checkMemberClass(mergeHandler.getClass());
this.mergeHandler = mergeHandler;
return (OP) this;
}
/**
* 选择需要下载的码率默认下载的码率
*
* @param bandWidth 指定的码率
*/
public OP setBandWidth(int bandWidth) {
this.bandWidth = bandWidth;
return (OP) this;
}
/**
* M3U8 bandWidth 码率url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param bandWidthUrlConverter {@link IBandWidthUrlConverter}
*/
public OP setBandWidthUrlConverter(IBandWidthUrlConverter bandWidthUrlConverter) {
CheckUtil.checkMemberClass(bandWidthUrlConverter.getClass());
this.bandWidthUrlConverter = bandWidthUrlConverter;
return (OP) this;
}
/**
* M3U8 密钥url转换器对于某些服务器密钥的下载地址是被加密的因此需要使用该方法将被加密的密钥解密成可被识别的http地址
*
* @param keyUrlConverter {@link IKeyUrlConverter}
*/
public OP setKeyUrlConverter(IKeyUrlConverter keyUrlConverter) {
CheckUtil.checkMemberClass(keyUrlConverter.getClass());
this.keyUrlConverter = keyUrlConverter;
return (OP) this;
}
}

@ -1,115 +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.download.m3u8;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.event.PeerIndexEvent;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.queue.DownloadTaskQueue;
import com.arialyy.aria.util.ALog;
/**
* m3u8点播文件参数设置
*/
public class M3U8VodDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
private DTaskWrapper mTaskWrapper;
M3U8VodDelegate(TARGET target, AbsTaskWrapper wrapper) {
super(target, wrapper);
mTaskWrapper = (DTaskWrapper) getTaskWrapper();
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_VOD);
}
/**
* 由于m3u8协议的特殊性质无法有效快速获取到正确到文件长度如果你需要显示文件中长度你需要自行设置文件长度
*
* @param fileSize 文件长度
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8VodDelegate<TARGET> setFileSize(long fileSize) {
if (fileSize <= 0) {
ALog.e(TAG, "文件长度错误");
return this;
}
mTaskWrapper.getEntity().setFileSize(fileSize);
return this;
}
/**
* 默认情况下对于同一点播文件的下载最多同时下载4个ts分片如果你希望增加或减少同时下载的ts分片数量可以使用该方法设置同时下载的ts分片数量
*
* @param num 同时下载的ts分片数量
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8VodDelegate<TARGET> setMaxTsQueueNum(int num) {
if (num < 1) {
ALog.e(TAG, "同时下载的分片数量不能小于1");
return this;
}
mTaskWrapper.asM3U8().setMaxTsQueueNum(num);
return this;
}
/**
* 启动任务时初始化索引位置
*
* 优先下载指定索引后的切片
* 如果指定的切片索引大于切片总数则此操作无效
* 如果指定的切片索引小于当前正在下载的切片索引并且指定索引和当前索引区间内有未下载的切片则优先下载该区间的切片否则此操作无效
* 如果指定索引后的切片已经全部下载完成但是索引前有未下载的切片间会自动下载未下载的切片
*
* @param index 指定的切片位置
*/
@CheckResult(suggest = Suggest.TO_CONTROLLER)
public M3U8VodDelegate<TARGET> setPeerIndex(int index) {
if (index < 1) {
ALog.e(TAG, "切片索引不能小于1");
return this;
}
mTaskWrapper.asM3U8().setJumpIndex(index);
return this;
}
/**
* 任务执行中跳转索引位置
* 优先下载指定索引后的切片
* 如果指定的切片索引大于切片总数则此操作无效
* 如果指定的切片索引小于当前正在下载的切片索引并且指定索引和当前索引区间内有未下载的切片则优先下载该区间的切片否则此操作无效
* 如果指定索引后的切片已经全部下载完成但是索引前有未下载的切片间会自动下载未下载的切片
*
* @param index 指定的切片位置
*/
public void jumPeerIndex(int index) {
if (index < 1) {
ALog.e(TAG, "切片索引不能小于1");
return;
}
if (!DownloadTaskQueue.getInstance().taskIsRunning(mTaskWrapper.getKey())) {
ALog.e(TAG,
String.format("任务【%s】没有运行,如果你希望在启动任务时初始化索引位置,请调用setPeerIndex(xxx)",
mTaskWrapper.getKey()));
return;
}
EventMsgUtil.getDefault().post(new PeerIndexEvent(mTaskWrapper.getKey(), index));
}
}

@ -0,0 +1,102 @@
/*
* 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.m3u8;
import com.arialyy.aria.core.processor.IVodTsUrlConverter;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
/**
* m3u8点播文件参数设置
*/
public class M3U8VodOption extends M3U8Option<M3U8VodOption> {
private long fileSize;
private int maxTsQueueNum;
private int jumpIndex;
private IVodTsUrlConverter vodUrlConverter;
public M3U8VodOption() {
super();
}
/**
* M3U8 ts 文件url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param vodUrlConverter {@link IVodTsUrlConverter}
*/
public M3U8VodOption setVodTsUrlConvert(IVodTsUrlConverter vodUrlConverter) {
CheckUtil.checkMemberClass(vodUrlConverter.getClass());
this.vodUrlConverter = vodUrlConverter;
return this;
}
/**
* 由于m3u8协议的特殊性质无法有效快速获取到正确到文件长度如果你需要显示文件中长度你需要自行设置文件长度
*
* @param fileSize 文件长度
*/
public M3U8VodOption setFileSize(long fileSize) {
if (fileSize <= 0) {
ALog.e(TAG, "文件长度错误");
return this;
}
this.fileSize = fileSize;
return this;
}
/**
* 默认情况下对于同一点播文件的下载最多同时下载4个ts分片如果你希望增加或减少同时下载的ts分片数量可以使用该方法设置同时下载的ts分片数量
*
* @param maxTsQueueNum 同时下载的ts分片数量
*/
public M3U8VodOption setMaxTsQueueNum(int maxTsQueueNum) {
if (maxTsQueueNum < 1) {
ALog.e(TAG, "同时下载的分片数量不能小于1");
return this;
}
this.maxTsQueueNum = maxTsQueueNum;
return this;
}
/**
* 启动任务时初始化索引位置
*
* 优先下载指定索引后的切片
* 如果指定的切片索引大于切片总数则此操作无效
* 如果指定的切片索引小于当前正在下载的切片索引并且指定索引和当前索引区间内有未下载的切片则优先下载该区间的切片否则此操作无效
* 如果指定索引后的切片已经全部下载完成但是索引前有未下载的切片间会自动下载未下载的切片
*
* @param jumpIndex 指定的切片位置
*/
public M3U8VodOption setPeerIndex(int jumpIndex) {
if (jumpIndex < 1) {
ALog.e(TAG, "切片索引不能小于1");
return this;
}
this.jumpIndex = jumpIndex;
return this;
}
public long getFileSize() {
return fileSize;
}
public int getJumpIndex() {
return jumpIndex;
}
}

@ -1,145 +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.download.m3u8;
import android.text.TextUtils;
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.download.DTaskWrapper;
import com.arialyy.aria.core.download.M3U8Listener;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.M3U8Exception;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* M3U8点播文件下载工具
* 工作流程
* 1创建一个和文件同父路径并且同名隐藏文件夹
* 2将所有m3u8的ts文件下载到该文件夹中
* 3完成所有分片下载后合并ts文件
* 4删除该隐藏文件夹
*/
public class M3U8VodUtil implements IUtil {
private final String TAG = "M3U8DownloadUtil";
private DTaskWrapper mWrapper;
private M3U8Listener mListener;
private boolean isStop = false, isCancel = false;
private List<String> mUrls = new ArrayList<>();
private M3U8VodLoader mLoader;
public M3U8VodUtil(DTaskWrapper wrapper, M3U8Listener listener) {
mWrapper = wrapper;
mListener = listener;
mLoader = new M3U8VodLoader(mListener, mWrapper);
}
@Override public String getKey() {
return mWrapper.getKey();
}
@Override public long getFileSize() {
return 0;
}
@Override public long getCurrentLocation() {
return 0;
}
@Override public boolean isRunning() {
return mLoader.isRunning();
}
@Override public void cancel() {
isCancel = true;
mLoader.cancel();
}
@Override public void stop() {
isStop = true;
mLoader.stop();
}
@Override public void start() {
if (isStop || isCancel) {
return;
}
mListener.onPre();
// peer数量小于0,
M3U8Entity m3U8Entity = mWrapper.getEntity().getM3U8Entity();
if (m3U8Entity.getPeerNum() <= 0 || (m3U8Entity.isGenerateIndexFile() && !new File(
String.format(M3U8InfoThread.M3U8_INDEX_FORMAT,
mWrapper.getEntity().getFilePath())).exists())) {
getVodInfo();
} else {
mLoader.start();
}
}
/**
* 获取点播文件信息
*/
private void getVodInfo() {
M3U8InfoThread thread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String key, CompleteInfo info) {
IVodTsUrlConverter converter = mWrapper.asM3U8().getVodUrlConverter();
if (converter != null) {
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) {
mUrls.addAll(converter.convert(mWrapper.getEntity().getUrl(), (List<String>) info.obj));
} else {
mUrls.addAll(
converter.convert(mWrapper.asM3U8().getBandWidthUrl(), (List<String>) info.obj));
}
} else {
mUrls.addAll((Collection<? extends String>) info.obj);
}
if (mUrls.isEmpty()) {
failDownload(new M3U8Exception(TAG, "获取地址失败"), false);
return;
} else if (!mUrls.get(0).startsWith("http")) {
failDownload(new M3U8Exception(TAG, "地址错误,请使用IM3U8UrlExtInfHandler处理你的url信息"), false);
return;
}
mWrapper.asM3U8().setUrls(mUrls);
if (isStop) {
mListener.onStop(mWrapper.getEntity().getCurrentProgress());
} else if (isCancel) {
mListener.onCancel();
} else {
mLoader.start();
}
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
failDownload(e, needRetry);
}
});
new Thread(thread).start();
}
private void failDownload(BaseException e, boolean needRetry) {
if (isStop || isCancel) {
return;
}
mListener.onFail(needRetry, e);
mLoader.onDestroy();
}
}

@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.download.DownloadGroupTask;
import com.arialyy.aria.core.event.ErrorEvent; import com.arialyy.aria.core.event.ErrorEvent;
import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.IConfigHandler; import com.arialyy.aria.core.inf.IConfigHandler;
import com.arialyy.aria.core.manager.SubTaskManager; import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; import com.arialyy.aria.core.queue.DGroupTaskQueue;
import com.arialyy.aria.core.task.DownloadGroupTask;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -44,9 +44,15 @@ abstract class AbsGroupConfigHandler<TARGET extends AbsTarget> implements IConfi
TAG = CommonUtil.getClassName(getClass()); TAG = CommonUtil.getClassName(getClass());
mTarget = target; mTarget = target;
mWrapper = TaskWrapperManager.getInstance().getGroupWrapper(DGTaskWrapper.class, taskId); mWrapper = TaskWrapperManager.getInstance().getGroupWrapper(DGTaskWrapper.class, taskId);
if (taskId != -1 && mWrapper.getEntity().getId() == -1) { // 判断已存在的任务
mWrapper.setErrorEvent(new ErrorEvent(taskId, String.format("没有id为%s的任务", taskId))); if (mTarget instanceof AbsNormalTarget) {
if (taskId < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务id为空"));
} else if (mWrapper.getEntity().getId() < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务信息不存在"));
} }
}
mTarget.setTaskWrapper(mWrapper); mTarget.setTaskWrapper(mWrapper);
if (getEntity() != null) { if (getEntity() != null) {
getTaskWrapper().setDirPathTemp(getEntity().getDirPath()); getTaskWrapper().setDirPathTemp(getEntity().getDirPath());
@ -58,10 +64,9 @@ abstract class AbsGroupConfigHandler<TARGET extends AbsTarget> implements IConfi
* *
* @return 子任务管理器 * @return 子任务管理器
*/ */
@CheckResult
SubTaskManager getSubTaskManager() { SubTaskManager getSubTaskManager() {
if (mSubTaskManager == null) { if (mSubTaskManager == null) {
mSubTaskManager = new SubTaskManager(mTarget.getTargetName(), getTaskWrapper()); mSubTaskManager = new SubTaskManager(getTaskWrapper());
} }
return mSubTaskManager; return mSubTaskManager;
} }
@ -77,11 +82,10 @@ abstract class AbsGroupConfigHandler<TARGET extends AbsTarget> implements IConfi
} }
@Override public boolean isRunning() { @Override public boolean isRunning() {
DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(getEntity().getKey()); DownloadGroupTask task = DGroupTaskQueue.getInstance().getTask(getEntity().getKey());
return task != null && task.isRunning(); return task != null && task.isRunning();
} }
@CheckResult
TARGET setDirPath(String dirPath) { TARGET setDirPath(String dirPath) {
mWrapper.setDirPathTemp(dirPath); mWrapper.setDirPathTemp(dirPath);
return mTarget; return mTarget;

@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.normal; package com.arialyy.aria.core.download.target;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.event.ErrorEvent; import com.arialyy.aria.core.event.ErrorEvent;
import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.IConfigHandler; import com.arialyy.aria.core.inf.IConfigHandler;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DownloadTaskQueue; import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
@ -40,26 +41,30 @@ class DNormalConfigHandler<TARGET extends AbsTarget> implements IConfigHandler {
/** /**
* @param taskId 第一次下载taskId为-1 * @param taskId 第一次下载taskId为-1
*/ */
DNormalConfigHandler(TARGET target, long taskId, String targetName) { DNormalConfigHandler(TARGET target, long taskId) {
this.mTarget = target; this.mTarget = target;
initTarget(taskId, targetName); initTarget(taskId);
} }
private void initTarget(long taskId, String targetName) { private void initTarget(long taskId) {
mWrapper = TaskWrapperManager.getInstance().getNormalTaskWrapper(DTaskWrapper.class, taskId); mWrapper = TaskWrapperManager.getInstance().getNormalTaskWrapper(DTaskWrapper.class, taskId);
if (taskId != -1 && mWrapper.getEntity().getId() == -1) { // 判断已存在的任务
mWrapper.setErrorEvent(new ErrorEvent(taskId, String.format("没有id为%s的任务", taskId))); if (mTarget instanceof AbsNormalTarget) {
if (taskId < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务id为空"));
} else if (mWrapper.getEntity().getId() < 0) {
mWrapper.setErrorEvent(new ErrorEvent(taskId, "任务信息不存在"));
}
} }
mEntity = mWrapper.getEntity();
mTarget.setTargetName(targetName); mEntity = mWrapper.getEntity();
mTarget.setTaskWrapper(mWrapper); mTarget.setTaskWrapper(mWrapper);
if (mEntity != null) { if (mEntity != null) {
getWrapper().setTempFilePath(mEntity.getFilePath()); getWrapper().setTempFilePath(mEntity.getFilePath());
} }
} }
public TARGET updateUrl(String newUrl) { TARGET updateUrl(String newUrl) {
if (TextUtils.isEmpty(newUrl)) { if (TextUtils.isEmpty(newUrl)) {
ALog.e(TAG, "url更新失败,newUrl为null"); ALog.e(TAG, "url更新失败,newUrl为null");
return mTarget; return mTarget;
@ -84,15 +89,16 @@ class DNormalConfigHandler<TARGET extends AbsTarget> implements IConfigHandler {
} }
@Override public boolean isRunning() { @Override public boolean isRunning() {
return DownloadTaskQueue.getInstance().taskIsRunning(mEntity.getKey()); return DTaskQueue.getInstance().taskIsRunning(mEntity.getKey());
} }
void setForceDownload(boolean forceDownload) { void setForceDownload(boolean forceDownload) {
getWrapper().setForceDownload(forceDownload); getWrapper().setIgnoreFilePathOccupy(forceDownload);
} }
void setUrl(String url) { void setUrl(String url) {
mEntity.setUrl(url); mEntity.setUrl(url);
mWrapper.setTempUrl(url);
} }
String getUrl() { String getUrl() {

@ -13,29 +13,30 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.normal; package com.arialyy.aria.core.download.target;
import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.common.AbsNormalTarget;
import java.util.List;
/** /**
* @Author aria * @author aria
* @Date 2019-09-05 * Date: 2019-09-05
*/ */
public class DNormalTargetFactory { public class DTargetFactory {
public static volatile DNormalTargetFactory INSTANCE; public static volatile DTargetFactory INSTANCE;
private DNormalTargetFactory() { private DTargetFactory() {
} }
public static DNormalTargetFactory getInstance() { public static DTargetFactory getInstance() {
if (INSTANCE == null) { if (INSTANCE == null) {
synchronized (DNormalTargetFactory.class) { synchronized (DTargetFactory.class) {
if (INSTANCE == null) { if (INSTANCE == null) {
INSTANCE = new DNormalTargetFactory(); INSTANCE = new DTargetFactory();
} }
} }
} }
@ -43,27 +44,37 @@ public class DNormalTargetFactory {
return INSTANCE; return INSTANCE;
} }
public <T extends AbsNormalTarget> T generateNormalTarget(Class<T> clazz, long taskId, public <T extends AbsNormalTarget> T generateNormalTarget(Class<T> clazz, long taskId) {
String targetName) {
T target = null; T target = null;
if (clazz == HttpNormalTarget.class) { if (clazz == HttpNormalTarget.class) {
target = (T) new HttpNormalTarget(taskId, targetName); target = (T) new HttpNormalTarget(taskId);
} else if (clazz == FtpNormalTarget.class) { } else if (clazz == FtpNormalTarget.class) {
target = (T) new FtpNormalTarget(taskId, targetName); target = (T) new FtpNormalTarget(taskId);
} else if (clazz == GroupNormalTarget.class) {
target = (T) new GroupNormalTarget(taskId);
} else if (clazz == FtpDirNormalTarget.class) {
target = (T) new FtpDirNormalTarget(taskId);
} }
return target; return target;
} }
public <T extends AbsBuilderTarget> T generateBuilderTarget(Class<T> clazz, String url, public <T extends AbsBuilderTarget> T generateBuilderTarget(Class<T> clazz, String url) {
String targetName) {
T target = null; T target = null;
if (clazz == HttpBuilderTarget.class) { if (clazz == HttpBuilderTarget.class) {
target = (T) new HttpBuilderTarget(url, targetName); target = (T) new HttpBuilderTarget(url);
} else if (clazz == FtpBuilderTarget.class) { } else if (clazz == FtpBuilderTarget.class) {
target = (T) new FtpBuilderTarget(url, targetName); target = (T) new FtpBuilderTarget(url);
} }
return target; return target;
} }
public FtpDirBuilderTarget generateDirBuilderTarget(String url) {
return new FtpDirBuilderTarget(url);
}
public GroupBuilderTarget generateGroupBuilderTarget(List<String> urls) {
return new GroupBuilderTarget(urls);
}
} }

@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.normal; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.ftp.FtpDelegate; import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
/** /**
@ -30,19 +28,38 @@ import com.arialyy.aria.util.CommonUtil;
public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> { public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
private DNormalConfigHandler<FtpBuilderTarget> mConfigHandler; private DNormalConfigHandler<FtpBuilderTarget> mConfigHandler;
FtpBuilderTarget(String url, String targetName) { FtpBuilderTarget(String url) {
mConfigHandler = new DNormalConfigHandler<>(this, -1, targetName); mConfigHandler = new DNormalConfigHandler<>(this, -1);
mConfigHandler.setUrl(url); mConfigHandler.setUrl(url);
getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(url));
getTaskWrapper().setRequestType(ITaskWrapper.D_FTP); getTaskWrapper().setRequestType(ITaskWrapper.D_FTP);
getEntity().setTaskType(ITaskWrapper.D_FTP);
getTaskWrapper().setNewTask(true);
} }
/** /**
* 设置登陆字符串编码ftps等参数 * 设置登陆字符串编码ftps等参数
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpBuilderTarget option(FtpOption option) {
public FtpDelegate<FtpBuilderTarget> option() { if (option == null) {
return new FtpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(mConfigHandler.getUrl()));
getTaskWrapper().getOptionParams().setParams(option);
return this;
}
/**
* 设置登陆字符串编码sftp等参数
*/
public FtpBuilderTarget sftpOption(SFtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(mConfigHandler.getUrl()));
getTaskWrapper().getOptionParams().setParams(option);
getEntity().setTaskType(ITaskWrapper.D_SFTP);
getTaskWrapper().setRequestType(ITaskWrapper.D_SFTP);
return this;
} }
/** /**
@ -51,8 +68,7 @@ public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
* 1如果保存路径是该文件的保存路径/mnt/sdcard/file.zip则使用路径中的文件名file.zip * 1如果保存路径是该文件的保存路径/mnt/sdcard/file.zip则使用路径中的文件名file.zip
* 2如果保存路径是文件夹路径/mnt/sdcard/则使用FTP服务器该文件的文件名 * 2如果保存路径是文件夹路径/mnt/sdcard/则使用FTP服务器该文件的文件名
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpBuilderTarget setFilePath(String filePath) {
public FtpBuilderTarget setFilePath(@NonNull String filePath) {
int lastIndex = mConfigHandler.getUrl().lastIndexOf("/"); int lastIndex = mConfigHandler.getUrl().lastIndexOf("/");
getEntity().setFileName(mConfigHandler.getUrl().substring(lastIndex + 1)); getEntity().setFileName(mConfigHandler.getUrl().substring(lastIndex + 1));
mConfigHandler.setTempFilePath(filePath); mConfigHandler.setTempFilePath(filePath);
@ -66,9 +82,10 @@ public class FtpBuilderTarget extends AbsBuilderTarget<FtpBuilderTarget> {
* *
* @param filePath 路径必须为文件路径不能为文件夹路径 * @param filePath 路径必须为文件路径不能为文件夹路径
* @param forceDownload {@code true}强制下载不考虑文件路径是否被占用 * @param forceDownload {@code true}强制下载不考虑文件路径是否被占用
* @deprecated 使用 {@link #ignoreFilePathOccupy()}
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) @Deprecated
public FtpBuilderTarget setFilePath(@NonNull String filePath, boolean forceDownload) { public FtpBuilderTarget setFilePath(String filePath, boolean forceDownload) {
mConfigHandler.setTempFilePath(filePath); mConfigHandler.setTempFilePath(filePath);
mConfigHandler.setForceDownload(forceDownload); mConfigHandler.setForceDownload(forceDownload);
return this; return this;

@ -13,12 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.ftp.FtpDelegate;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.manager.SubTaskManager; import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -29,12 +27,13 @@ import com.arialyy.aria.util.CommonUtil;
*/ */
public class FtpDirBuilderTarget extends AbsBuilderTarget<FtpDirBuilderTarget> { public class FtpDirBuilderTarget extends AbsBuilderTarget<FtpDirBuilderTarget> {
private FtpDirConfigHandler<FtpDirBuilderTarget> mConfigHandler; private FtpDirConfigHandler<FtpDirBuilderTarget> mConfigHandler;
private String url;
FtpDirBuilderTarget(String url, String targetName) { FtpDirBuilderTarget(String url) {
setTargetName(targetName); this.url = url;
mConfigHandler = new FtpDirConfigHandler<>(this, -1); mConfigHandler = new FtpDirConfigHandler<>(this, -1);
getEntity().setGroupHash(url); getEntity().setGroupHash(url);
getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(url)); getTaskWrapper().setNewTask(true);
} }
/** /**
@ -56,7 +55,6 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget<FtpDirBuilderTarget> {
* *
* @param dirPath 任务组保存文件夹路径 * @param dirPath 任务组保存文件夹路径
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public FtpDirBuilderTarget setDirPath(String dirPath) { public FtpDirBuilderTarget setDirPath(String dirPath) {
return mConfigHandler.setDirPath(dirPath); return mConfigHandler.setDirPath(dirPath);
} }
@ -64,7 +62,6 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget<FtpDirBuilderTarget> {
/** /**
* 设置任务组别名 * 设置任务组别名
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public FtpDirBuilderTarget setGroupAlias(String alias) { public FtpDirBuilderTarget setGroupAlias(String alias) {
mConfigHandler.setGroupAlias(alias); mConfigHandler.setGroupAlias(alias);
return this; return this;
@ -73,9 +70,13 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget<FtpDirBuilderTarget> {
/** /**
* 设置登陆字符串编码ftps等参数 * 设置登陆字符串编码ftps等参数
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpDirBuilderTarget option(FtpOption option) {
public FtpDelegate<FtpDirBuilderTarget> option() { if (option == null) {
return new FtpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(url));
getTaskWrapper().getOptionParams().setParams(option);
return this;
} }
@Override public DownloadGroupEntity getEntity() { @Override public DownloadGroupEntity getEntity() {
@ -87,7 +88,6 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget<FtpDirBuilderTarget> {
* *
* @return 子任务管理器 * @return 子任务管理器
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public SubTaskManager getSubTaskManager() { public SubTaskManager getSubTaskManager() {
return mConfigHandler.getSubTaskManager(); return mConfigHandler.getSubTaskManager();
} }

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil;
import java.util.List; import java.util.List;
/** /**
@ -33,11 +35,12 @@ class FtpDirConfigHandler<TARGET extends AbsTarget> extends AbsGroupConfigHandle
} }
private void init() { private void init() {
getTaskWrapper().setRequestType(AbsTaskWrapper.D_FTP_DIR); getTaskWrapper().setRequestType(ITaskWrapper.D_FTP_DIR);
List<DTaskWrapper> wrappers = getTaskWrapper().getSubTaskWrapper(); List<DTaskWrapper> wrappers = getTaskWrapper().getSubTaskWrapper();
if (!wrappers.isEmpty()) { if (!wrappers.isEmpty()) {
for (DTaskWrapper subWrapper : wrappers) { for (DTaskWrapper subWrapper : wrappers) {
subWrapper.setRequestType(ITaskWrapper.D_FTP); subWrapper.setRequestType(ITaskWrapper.D_FTP);
subWrapper.getEntity().setTaskType(ITaskWrapper.D_FTP);
} }
} }
} }

@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.ftp.FtpDelegate;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
import com.arialyy.aria.core.inf.IOptionConstant;
import com.arialyy.aria.core.manager.SubTaskManager; import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -29,11 +28,11 @@ import com.arialyy.aria.util.CommonUtil;
*/ */
public class FtpDirNormalTarget extends AbsNormalTarget<FtpDirNormalTarget> { public class FtpDirNormalTarget extends AbsNormalTarget<FtpDirNormalTarget> {
private FtpDirConfigHandler<FtpDirNormalTarget> mConfigHandler; private FtpDirConfigHandler<FtpDirNormalTarget> mConfigHandler;
private FtpOption option;
FtpDirNormalTarget(long taskId, String targetName) { FtpDirNormalTarget(long taskId) {
setTargetName(targetName);
mConfigHandler = new FtpDirConfigHandler<>(this, taskId); mConfigHandler = new FtpDirConfigHandler<>(this, taskId);
getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getKey())); getTaskWrapper().setNewTask(false);
} }
@Override public boolean isRunning() { @Override public boolean isRunning() {
@ -63,7 +62,6 @@ public class FtpDirNormalTarget extends AbsNormalTarget<FtpDirNormalTarget> {
* *
* @param dirPath 任务组保存文件夹路径 * @param dirPath 任务组保存文件夹路径
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public FtpDirNormalTarget modifyDirPath(String dirPath) { public FtpDirNormalTarget modifyDirPath(String dirPath) {
return mConfigHandler.setDirPath(dirPath); return mConfigHandler.setDirPath(dirPath);
} }
@ -71,9 +69,12 @@ public class FtpDirNormalTarget extends AbsNormalTarget<FtpDirNormalTarget> {
/** /**
* 设置登陆字符串编码ftps等参数 * 设置登陆字符串编码ftps等参数
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpDirNormalTarget option(FtpOption option) {
public FtpDelegate<FtpDirNormalTarget> option() { if (option == null) {
return new FtpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("ftp 任务配置为空");
}
this.option = option;
return this;
} }
@Override public DownloadGroupEntity getEntity() { @Override public DownloadGroupEntity getEntity() {
@ -85,8 +86,16 @@ public class FtpDirNormalTarget extends AbsNormalTarget<FtpDirNormalTarget> {
* *
* @return 子任务管理器 * @return 子任务管理器
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public SubTaskManager getSubTaskManager() { public SubTaskManager getSubTaskManager() {
return mConfigHandler.getSubTaskManager(); return mConfigHandler.getSubTaskManager();
} }
@Override protected void onPre() {
super.onPre();
if (option == null) {
option = new FtpOption();
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getKey()));
getTaskWrapper().getOptionParams().setParams(option);
}
} }

@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.normal; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.FtpOption;
import com.arialyy.aria.core.common.ftp.FtpDelegate; import com.arialyy.aria.core.common.SFtpOption;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
/** /**
@ -31,18 +29,36 @@ import com.arialyy.aria.util.CommonUtil;
public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> { public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> {
private DNormalConfigHandler<FtpNormalTarget> mConfigHandler; private DNormalConfigHandler<FtpNormalTarget> mConfigHandler;
FtpNormalTarget(long taskId, String targetName) { FtpNormalTarget(long taskId) {
mConfigHandler = new DNormalConfigHandler<>(this, taskId, targetName); mConfigHandler = new DNormalConfigHandler<>(this, taskId);
getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getUrl()));
getTaskWrapper().setRequestType(ITaskWrapper.D_FTP); getTaskWrapper().setRequestType(ITaskWrapper.D_FTP);
getTaskWrapper().setNewTask(false);
} }
/** /**
* 设置登陆字符串编码ftps等参数 * 设置登陆字符串编码ftps等参数
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpNormalTarget option(FtpOption option) {
public FtpDelegate<FtpNormalTarget> option() { if (option == null) {
return new FtpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getUrl()));
getTaskWrapper().getOptionParams().setParams(option);
return this;
}
/**
* 设置登陆字符串编码sftp等参数
*/
public FtpNormalTarget sftpOption(SFtpOption option) {
if (option == null) {
throw new NullPointerException("ftp 任务配置为空");
}
option.setUrlEntity(CommonUtil.getFtpUrlInfo(mConfigHandler.getUrl()));
getTaskWrapper().getOptionParams().setParams(option);
getEntity().setTaskType(ITaskWrapper.D_SFTP);
getTaskWrapper().setRequestType(ITaskWrapper.D_SFTP);
return this;
} }
/** /**
@ -51,8 +67,7 @@ public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> {
* 1如果保存路径是该文件的保存路径/mnt/sdcard/file.zip则使用路径中的文件名file.zip * 1如果保存路径是该文件的保存路径/mnt/sdcard/file.zip则使用路径中的文件名file.zip
* 2如果保存路径是文件夹路径/mnt/sdcard/则使用FTP服务器该文件的文件名 * 2如果保存路径是文件夹路径/mnt/sdcard/则使用FTP服务器该文件的文件名
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpNormalTarget modifyFilePath(String filePath) {
public FtpNormalTarget modifyFilePath(@NonNull String filePath) {
int lastIndex = mConfigHandler.getUrl().lastIndexOf("/"); int lastIndex = mConfigHandler.getUrl().lastIndexOf("/");
getEntity().setFileName(mConfigHandler.getUrl().substring(lastIndex + 1)); getEntity().setFileName(mConfigHandler.getUrl().substring(lastIndex + 1));
mConfigHandler.setTempFilePath(filePath); mConfigHandler.setTempFilePath(filePath);
@ -62,7 +77,6 @@ public class FtpNormalTarget extends AbsNormalTarget<FtpNormalTarget> {
/** /**
* 更新下载地址 * 更新下载地址
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public FtpNormalTarget updateUrl(String newUrl) { public FtpNormalTarget updateUrl(String newUrl) {
return mConfigHandler.updateUrl(newUrl); return mConfigHandler.updateUrl(newUrl);
} }

@ -13,16 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.HttpOption;
import com.arialyy.aria.core.common.http.HttpDelegate; import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.core.manager.SubTaskManager; import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import java.util.List; import java.util.List;
@ -33,19 +31,22 @@ import java.util.List;
public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> { public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
private HttpGroupConfigHandler<GroupBuilderTarget> mConfigHandler; private HttpGroupConfigHandler<GroupBuilderTarget> mConfigHandler;
GroupBuilderTarget(List<String> urls, String targetName) { GroupBuilderTarget(List<String> urls) {
setTargetName(targetName);
mConfigHandler = new HttpGroupConfigHandler<>(this, -1); mConfigHandler = new HttpGroupConfigHandler<>(this, -1);
getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP);
mConfigHandler.setGroupUrl(urls); mConfigHandler.setGroupUrl(urls);
getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP);
getTaskWrapper().setNewTask(true);
} }
/** /**
* 设置http请求参数header等信息 * 设置http请求参数header等信息
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupBuilderTarget option(HttpOption option) {
public HttpDelegate<GroupBuilderTarget> option() { if (option == null) {
return new HttpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("任务配置为空");
}
getTaskWrapper().getOptionParams().setParams(option);
return this;
} }
/** /**
@ -56,7 +57,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
* *
* @param fileSize 任务组总大小 * @param fileSize 任务组总大小
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupBuilderTarget setFileSize(long fileSize) { public GroupBuilderTarget setFileSize(long fileSize) {
if (fileSize <= 0) { if (fileSize <= 0) {
ALog.e(TAG, "文件大小不能小于 0"); ALog.e(TAG, "文件大小不能小于 0");
@ -75,7 +75,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
* 2如果你的知道组合任务的总长度请使用{@link #setFileSize(long)}设置组合任务的长度 * 2如果你的知道组合任务的总长度请使用{@link #setFileSize(long)}设置组合任务的长度
* 3由于网络或其它原因的存在这种方式获取的组合任务大小有可能是不准确的 * 3由于网络或其它原因的存在这种方式获取的组合任务大小有可能是不准确的
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupBuilderTarget unknownSize() { public GroupBuilderTarget unknownSize() {
((DGTaskWrapper) getTaskWrapper()).setUnknownSize(true); ((DGTaskWrapper) getTaskWrapper()).setUnknownSize(true);
return this; return this;
@ -86,7 +85,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
* *
* @return 子任务管理器 * @return 子任务管理器
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public SubTaskManager getSubTaskManager() { public SubTaskManager getSubTaskManager() {
return mConfigHandler.getSubTaskManager(); return mConfigHandler.getSubTaskManager();
} }
@ -96,7 +94,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
* *
* @deprecated {@link #setSubFileName(List)} 请使用该api * @deprecated {@link #setSubFileName(List)} 请使用该api
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
@Deprecated public GroupBuilderTarget setSubTaskFileName(List<String> subTaskFileName) { @Deprecated public GroupBuilderTarget setSubTaskFileName(List<String> subTaskFileName) {
return setSubFileName(subTaskFileName); return setSubFileName(subTaskFileName);
} }
@ -104,12 +101,19 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
/** /**
* 设置任务组别名 * 设置任务组别名
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupBuilderTarget setGroupAlias(String alias) { public GroupBuilderTarget setGroupAlias(String alias) {
mConfigHandler.setGroupAlias(alias); mConfigHandler.setGroupAlias(alias);
return this; return this;
} }
/**
* {@code true} 忽略任务冲突不考虑组任务hash冲突的情况
*/
public GroupBuilderTarget ignoreTaskOccupy() {
((AbsGroupTaskWrapper) getTaskWrapper()).setIgnoreTaskOccupy(true);
return this;
}
/** /**
* 设置任务组的文件夹路径在Aria中任务组的所有子任务都会下载到以任务组组名的文件夹中 * 设置任务组的文件夹路径在Aria中任务组的所有子任务都会下载到以任务组组名的文件夹中
* groupDirPath = "/mnt/sdcard/download/group_test" * groupDirPath = "/mnt/sdcard/download/group_test"
@ -129,7 +133,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
* *
* @param dirPath 任务组保存文件夹路径 * @param dirPath 任务组保存文件夹路径
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupBuilderTarget setDirPath(String dirPath) { public GroupBuilderTarget setDirPath(String dirPath) {
return mConfigHandler.setDirPath(dirPath); return mConfigHandler.setDirPath(dirPath);
} }
@ -137,21 +140,7 @@ public class GroupBuilderTarget extends AbsBuilderTarget<GroupBuilderTarget> {
/** /**
* 设置子任务文件名该方法必须在{@link #setDirPath(String)}之后调用否则不生效 * 设置子任务文件名该方法必须在{@link #setDirPath(String)}之后调用否则不生效
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupBuilderTarget setSubFileName(List<String> subTaskFileName) { public GroupBuilderTarget setSubFileName(List<String> subTaskFileName) {
return mConfigHandler.setSubFileName(subTaskFileName); return mConfigHandler.setSubFileName(subTaskFileName);
} }
/**
* 如果你需要使用header中特定的key来设置文件长度或有定制文件长度的需要那么你可以通过该方法自行处理文件长度
*/
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupBuilderTarget setFileLenAdapter(IHttpFileLenAdapter adapter) {
if (adapter == null) {
throw new IllegalArgumentException("adapter为空");
}
getTaskWrapper().asHttp().setFileLenAdapter(adapter);
return this;
}
} }

@ -13,14 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.HttpOption;
import com.arialyy.aria.core.common.http.HttpDelegate; import com.arialyy.aria.core.download.AbsGroupTaskWrapper;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.download.DGTaskWrapper;
import com.arialyy.aria.core.manager.SubTaskManager; import com.arialyy.aria.core.manager.SubTaskManager;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog;
import java.util.List; import java.util.List;
/** /**
@ -30,18 +31,21 @@ import java.util.List;
public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> { public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
private HttpGroupConfigHandler<GroupNormalTarget> mConfigHandler; private HttpGroupConfigHandler<GroupNormalTarget> mConfigHandler;
GroupNormalTarget(long taskId, String targetName) { GroupNormalTarget(long taskId) {
setTargetName(targetName);
mConfigHandler = new HttpGroupConfigHandler<>(this, taskId); mConfigHandler = new HttpGroupConfigHandler<>(this, taskId);
getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP); getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP);
getTaskWrapper().setNewTask(false);
} }
/** /**
* 设置http请求参数header等信息 * 设置http请求参数header等信息
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupNormalTarget option(HttpOption option) {
public HttpDelegate<GroupNormalTarget> option() { if (option == null) {
return new HttpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("任务配置为空");
}
getTaskWrapper().getOptionParams().setParams(option);
return this;
} }
/** /**
@ -49,7 +53,6 @@ public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
* *
* @return 子任务管理器 * @return 子任务管理器
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public SubTaskManager getSubTaskManager() { public SubTaskManager getSubTaskManager() {
return mConfigHandler.getSubTaskManager(); return mConfigHandler.getSubTaskManager();
} }
@ -57,7 +60,6 @@ public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
/** /**
* 设置任务组别名 * 设置任务组别名
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupNormalTarget setGroupAlias(String alias) { public GroupNormalTarget setGroupAlias(String alias) {
mConfigHandler.setGroupAlias(alias); mConfigHandler.setGroupAlias(alias);
return this; return this;
@ -68,11 +70,18 @@ public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
* *
* @param urls 新的组合任务下载地址列表 * @param urls 新的组合任务下载地址列表
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupNormalTarget updateUrls(List<String> urls) { public GroupNormalTarget updateUrls(List<String> urls) {
return mConfigHandler.updateUrls(urls); return mConfigHandler.updateUrls(urls);
} }
/**
* {@code true} 忽略任务冲突不考虑组任务hash冲突的情况
*/
public GroupNormalTarget ignoreTaskOccupy() {
((AbsGroupTaskWrapper) getTaskWrapper()).setIgnoreTaskOccupy(true);
return this;
}
/** /**
* 更新任务组的文件夹路径在Aria中任务组的所有子任务都会下载到以任务组组名的文件夹中 * 更新任务组的文件夹路径在Aria中任务组的所有子任务都会下载到以任务组组名的文件夹中
* groupDirPath = "/mnt/sdcard/download/group_test" * groupDirPath = "/mnt/sdcard/download/group_test"
@ -92,7 +101,6 @@ public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
* *
* @param dirPath 任务组保存文件夹路径 * @param dirPath 任务组保存文件夹路径
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupNormalTarget modifyDirPath(String dirPath) { public GroupNormalTarget modifyDirPath(String dirPath) {
return mConfigHandler.setDirPath(dirPath); return mConfigHandler.setDirPath(dirPath);
} }
@ -100,11 +108,41 @@ public class GroupNormalTarget extends AbsNormalTarget<GroupNormalTarget> {
/** /**
* 更新子任务文件名该方法必须在{@link #modifyDirPath(String)}之后调用否则不生效 * 更新子任务文件名该方法必须在{@link #modifyDirPath(String)}之后调用否则不生效
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public GroupNormalTarget modifySubFileName(List<String> subTaskFileName) { public GroupNormalTarget modifySubFileName(List<String> subTaskFileName) {
return mConfigHandler.setSubFileName(subTaskFileName); return mConfigHandler.setSubFileName(subTaskFileName);
} }
/**
* 如果无法获取到组合任务到总长度请调用该方法
* 请注意
* 1如果组合任务到子任务数过多请不要使用该标志位否则Aria将需要消耗大量的时间获取组合任务的总长度直到获取完成组合任务总长度后才会执行下载
* 2如果你的知道组合任务的总长度请使用{@link #setFileSize(long)}设置组合任务的长度
* 3由于网络或其它原因的存在这种方式获取的组合任务大小有可能是不准确的
*/
public GroupNormalTarget unknownSize() {
((DGTaskWrapper) getTaskWrapper()).setUnknownSize(true);
return this;
}
/**
* 任务组总任务大小任务组是一个抽象的概念没有真实的数据实体任务组的大小是Aria动态获取子任务大小相加而得到的
* 如果你知道当前任务组总大小你也可以调用该方法给任务组设置大小
*
* 为了更好的用户体验组合任务最好设置文件大小默认需要强制设置文件大小如果无法获取到总长度请调用{@link #unknownSize()}
*
* @param fileSize 任务组总大小
*/
public GroupNormalTarget setFileSize(long fileSize) {
if (fileSize <= 0) {
ALog.e(TAG, "文件大小不能小于 0");
return this;
}
if (getEntity().getFileSize() <= 1 || getEntity().getFileSize() != fileSize) {
getEntity().setFileSize(fileSize);
}
return this;
}
@Override public boolean isRunning() { @Override public boolean isRunning() {
return mConfigHandler.isRunning(); return mConfigHandler.isRunning();
} }

@ -13,48 +13,56 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.normal; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.HttpOption;
import com.arialyy.aria.core.common.http.HttpDelegate; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.m3u8.M3U8Delegate; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.core.download.m3u8.M3U8LiveOption;
import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.download.m3u8.M3U8VodOption;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
public class HttpBuilderTarget extends AbsBuilderTarget<HttpBuilderTarget> { public class HttpBuilderTarget extends AbsBuilderTarget<HttpBuilderTarget> {
private DNormalConfigHandler<HttpBuilderTarget> mConfigHandler; private DNormalConfigHandler<HttpBuilderTarget> mConfigHandler;
HttpBuilderTarget(String url, String targetName) { HttpBuilderTarget(String url) {
mConfigHandler = new DNormalConfigHandler<>(this, -1, targetName); mConfigHandler = new DNormalConfigHandler<>(this, -1);
getTaskWrapper().setRequestType(ITaskWrapper.D_HTTP);
mConfigHandler.setUrl(url); mConfigHandler.setUrl(url);
getTaskWrapper().setRequestType(ITaskWrapper.D_HTTP);
getTaskWrapper().setNewTask(true);
((DownloadEntity)getEntity()).setTaskType(ITaskWrapper.D_HTTP);
} }
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public M3U8Delegate<HttpBuilderTarget> asM3U8() { public HttpBuilderTarget m3u8VodOption(M3U8VodOption m3U8VodOption) {
return new M3U8Delegate<>(this, getTaskWrapper()); if (m3U8VodOption == null) {
throw new NullPointerException("m3u8任务设置为空");
}
getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_VOD);
getTaskWrapper().getEntity().setFileSize(m3U8VodOption.getFileSize());
((DTaskWrapper) getTaskWrapper()).getM3U8Params().setParams(m3U8VodOption);
return this;
} }
/** public HttpBuilderTarget m3u8LiveOption(M3U8LiveOption m3U8LiveOption) {
* 设置http请求参数header等信息 if (m3U8LiveOption == null) {
*/ throw new NullPointerException("m3u8任务设置为空");
@CheckResult(suggest = Suggest.TASK_CONTROLLER) }
public HttpDelegate<HttpBuilderTarget> option() { getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE);
return new HttpDelegate<>(this, getTaskWrapper()); ((DTaskWrapper) getTaskWrapper()).getM3U8Params().setParams(m3U8LiveOption);
return this;
} }
/** /**
* 是否使用服务器通过content-disposition传递的文件名内容格式{@code attachment;filename=***} * 设置http请求参数header等信息
* 如果获取不到服务器文件名则使用用户设置的文件名
*
* @param use {@code true} 使用
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpBuilderTarget option(HttpOption option) {
public HttpBuilderTarget useServerFileName(boolean use) { if (option == null) {
getTaskWrapper().asHttp().setUseServerFileName(use); throw new NullPointerException("任务配置为空");
}
getTaskWrapper().getOptionParams().setParams(option);
return this; return this;
} }
@ -65,8 +73,7 @@ public class HttpBuilderTarget extends AbsBuilderTarget<HttpBuilderTarget> {
* *
* @param filePath 路径必须为文件路径不能为文件夹路径 * @param filePath 路径必须为文件路径不能为文件夹路径
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpBuilderTarget setFilePath(String filePath) {
public HttpBuilderTarget setFilePath(@NonNull String filePath) {
mConfigHandler.setTempFilePath(filePath); mConfigHandler.setTempFilePath(filePath);
return this; return this;
} }
@ -78,23 +85,12 @@ public class HttpBuilderTarget extends AbsBuilderTarget<HttpBuilderTarget> {
* *
* @param filePath 路径必须为文件路径不能为文件夹路径 * @param filePath 路径必须为文件路径不能为文件夹路径
* @param forceDownload {@code true}强制下载不考虑文件路径是否被占用 * @param forceDownload {@code true}强制下载不考虑文件路径是否被占用
* @deprecated 使用 {@link #ignoreFilePathOccupy()}
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) @Deprecated
public HttpBuilderTarget setFilePath(@NonNull String filePath, boolean forceDownload) { public HttpBuilderTarget setFilePath(String filePath, boolean forceDownload) {
mConfigHandler.setTempFilePath(filePath); mConfigHandler.setTempFilePath(filePath);
mConfigHandler.setForceDownload(forceDownload); mConfigHandler.setForceDownload(forceDownload);
return this; return this;
} }
/**
* 如果你需要使用header中特定的key来设置文件长度或有定制文件长度的需要那么你可以通过该方法自行处理文件长度
*/
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public HttpBuilderTarget setFileLenAdapter(IHttpFileLenAdapter adapter) {
if (adapter == null) {
throw new IllegalArgumentException("adapter为空");
}
getTaskWrapper().asHttp().setFileLenAdapter(adapter);
return this;
}
} }

@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.group; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -59,6 +59,7 @@ class HttpGroupConfigHandler<TARGET extends AbsTarget> extends AbsGroupConfigHan
List<DownloadEntity> subEntities = DbDataHelper.createHttpSubTask(groupHash, mUrls); List<DownloadEntity> subEntities = DbDataHelper.createHttpSubTask(groupHash, mUrls);
List<DTaskWrapper> wrappers = new ArrayList<>(); List<DTaskWrapper> wrappers = new ArrayList<>();
for (DownloadEntity subEntity : subEntities) { for (DownloadEntity subEntity : subEntities) {
subEntity.setTaskType(ITaskWrapper.D_HTTP);
wrappers.add(new DTaskWrapper(subEntity)); wrappers.add(new DTaskWrapper(subEntity));
} }
getEntity().setUrls(urls); getEntity().setUrls(urls);
@ -69,7 +70,6 @@ class HttpGroupConfigHandler<TARGET extends AbsTarget> extends AbsGroupConfigHan
/** /**
* 设置子任务文件名该方法必须在{@link #setDirPath(String)}之后调用否则不生效 * 设置子任务文件名该方法必须在{@link #setDirPath(String)}之后调用否则不生效
*/ */
@CheckResult
TARGET setSubFileName(List<String> subTaskFileName) { TARGET setSubFileName(List<String> subTaskFileName) {
if (subTaskFileName == null || subTaskFileName.isEmpty()) { if (subTaskFileName == null || subTaskFileName.isEmpty()) {
ALog.w(TAG, "修改子任务的文件名失败:列表为null"); ALog.w(TAG, "修改子任务的文件名失败:列表为null");
@ -90,7 +90,6 @@ class HttpGroupConfigHandler<TARGET extends AbsTarget> extends AbsGroupConfigHan
* *
* @param urls 新的组合任务下载地址列表 * @param urls 新的组合任务下载地址列表
*/ */
@CheckResult
TARGET updateUrls(List<String> urls) { TARGET updateUrls(List<String> urls) {
if (urls == null || urls.isEmpty()) { if (urls == null || urls.isEmpty()) {
throw new NullPointerException("下载地址列表为空"); throw new NullPointerException("下载地址列表为空");

@ -13,14 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.download.normal; package com.arialyy.aria.core.download.target;
import androidx.annotation.CheckResult;
import com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.common.Suggest; import com.arialyy.aria.core.common.HttpOption;
import com.arialyy.aria.core.common.http.HttpDelegate; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.m3u8.M3U8Delegate; import com.arialyy.aria.core.download.m3u8.M3U8LiveOption;
import com.arialyy.aria.core.download.m3u8.M3U8VodOption;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
/** /**
* Created by lyy on 2016/12/5. * Created by lyy on 2016/12/5.
@ -29,22 +30,44 @@ import com.arialyy.aria.core.download.m3u8.M3U8Delegate;
public class HttpNormalTarget extends AbsNormalTarget<HttpNormalTarget> { public class HttpNormalTarget extends AbsNormalTarget<HttpNormalTarget> {
private DNormalConfigHandler<HttpNormalTarget> mConfigHandler; private DNormalConfigHandler<HttpNormalTarget> mConfigHandler;
HttpNormalTarget(long taskId, String targetName) { HttpNormalTarget(long taskId) {
mConfigHandler = new DNormalConfigHandler<>(this, taskId, targetName); mConfigHandler = new DNormalConfigHandler<>(this, taskId);
getTaskWrapper().setRequestType(getTaskWrapper().getEntity().getTaskType()); getTaskWrapper().setRequestType(getTaskWrapper().getEntity().getTaskType());
getTaskWrapper().setNewTask(false);
} }
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public M3U8NormalTarget m3u8VodOption(M3U8VodOption m3U8VodOption) {
public M3U8Delegate<HttpNormalTarget> asM3U8() { if (m3U8VodOption == null) {
return new M3U8Delegate<>(this, getTaskWrapper()); throw new NullPointerException("m3u8任务设置为空");
}
getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_VOD);
getTaskWrapper().getEntity().setFileSize(m3U8VodOption.getFileSize());
((DTaskWrapper) getTaskWrapper()).getM3U8Params().setParams(m3U8VodOption);
return new M3U8NormalTarget((DTaskWrapper) getTaskWrapper());
}
public M3U8NormalTarget m3u8VodOption() {
return new M3U8NormalTarget((DTaskWrapper) getTaskWrapper());
}
public HttpNormalTarget m3u8LiveOption(M3U8LiveOption m3U8LiveOption) {
if (m3U8LiveOption == null) {
throw new NullPointerException("m3u8任务设置为空");
}
getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE);
((DTaskWrapper) getTaskWrapper()).getM3U8Params().setParams(m3U8LiveOption);
return this;
} }
/** /**
* 设置http请求参数header等信息 * 设置http请求参数header等信息
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpNormalTarget option(HttpOption option) {
public HttpDelegate<HttpNormalTarget> option() { if (option == null) {
return new HttpDelegate<>(this, getTaskWrapper()); throw new NullPointerException("任务配置为空");
}
getTaskWrapper().getOptionParams().setParams(option);
return this;
} }
/** /**
@ -52,7 +75,6 @@ public class HttpNormalTarget extends AbsNormalTarget<HttpNormalTarget> {
* 原文件路径 /mnt/sdcard/test.zip * 原文件路径 /mnt/sdcard/test.zip
* 如果需要将test.zip改为game.zip只需要重新设置文件路径为/mnt/sdcard/game.zip * 如果需要将test.zip改为game.zip只需要重新设置文件路径为/mnt/sdcard/game.zip
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public HttpNormalTarget modifyFilePath(String filePath) { public HttpNormalTarget modifyFilePath(String filePath) {
mConfigHandler.setTempFilePath(filePath); mConfigHandler.setTempFilePath(filePath);
return this; return this;
@ -68,7 +90,6 @@ public class HttpNormalTarget extends AbsNormalTarget<HttpNormalTarget> {
/** /**
* 更新下载地址 * 更新下载地址
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public HttpNormalTarget updateUrl(String newUrl) { public HttpNormalTarget updateUrl(String newUrl) {
return mConfigHandler.updateUrl(newUrl); return mConfigHandler.updateUrl(newUrl);
} }

@ -0,0 +1,59 @@
package com.arialyy.aria.core.download.target;
/*
* 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.
*/
import com.arialyy.aria.core.common.AbsNormalTarget;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.event.EventMsgUtil;
import com.arialyy.aria.core.event.PeerIndexEvent;
import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
import com.arialyy.aria.util.ALog;
public class M3U8NormalTarget extends AbsNormalTarget<M3U8NormalTarget> {
M3U8NormalTarget(DTaskWrapper wrapper) {
setTaskWrapper(wrapper);
getTaskWrapper().setNewTask(false);
getTaskWrapper().setRequestType(ITaskWrapper.M3U8_VOD);
}
/**
* 任务执行中跳转索引位置
* 优先下载指定索引后的切片
* 如果指定的切片索引大于切片总数则此操作无效
* 如果指定的切片索引小于当前正在下载的切片索引并且指定索引和当前索引区间内有未下载的切片则优先下载该区间的切片否则此操作无效
* 如果指定索引后的切片已经全部下载完成但是索引前有未下载的切片间会自动下载未下载的切片
*
* @param index 指定的切片位置
*/
public void jumPeerIndex(int index) {
if (index < 1) {
ALog.e(TAG, "切片索引不能小于1");
return;
}
if (!DTaskQueue.getInstance().taskIsRunning(getTaskWrapper().getKey())) {
ALog.e(TAG,
String.format("任务【%s】没有运行,如果你希望在启动任务时初始化索引位置,请调用setPeerIndex(xxx)",
getTaskWrapper().getKey()));
return;
}
EventMsgUtil.getDefault().post(new PeerIndexEvent(getTaskWrapper().getKey(), index));
}
}

@ -0,0 +1,56 @@
/*
* 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.target;
import com.arialyy.aria.core.common.AbsBuilderTarget;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.wrapper.ITaskWrapper;
/**
* @author aria
* Date: 2019-09-06
*/
public class TcpBuilderTarget extends AbsBuilderTarget<TcpBuilderTarget> {
private DNormalConfigHandler mConfigHandler;
TcpBuilderTarget(String ip, int port) {
mConfigHandler = new DNormalConfigHandler<>(this, -1);
getTaskWrapper().setRequestType(ITaskWrapper.D_TCP);
((DownloadEntity) getEntity()).setTaskType(ITaskWrapper.D_TCP);
getTaskWrapper().setNewTask(true);
}
//
///**
// * 设置tcp相应信息
// */
//@CheckResult(suggest = Suggest.TASK_CONTROLLER)
//public TcpBuilderTarget option(T) {
// return new TcpDelegate<>(this, getTaskWrapper());
//}
/**
* 设置文件存储路径如果需要修改新的文件名修改路径便可
* 原文件路径 /mnt/sdcard/test.zip
* 如果需要将test.zip改为game.zip只需要重新设置文件路径为/mnt/sdcard/game.zip
*
* @param filePath 路径必须为文件路径不能为文件夹路径
*/
public TcpBuilderTarget setFilePath(String filePath) {
mConfigHandler.setTempFilePath(filePath);
return this;
}
}

@ -0,0 +1,90 @@
/*
* 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.tcp;
import android.text.TextUtils;
import com.arialyy.aria.core.common.BaseOption;
import com.arialyy.aria.util.ALog;
import java.nio.charset.Charset;
/**
* @author aria
* Date: 2019-09-06
*/
public class TcpDelegate extends BaseOption {
private String params;
private String heartbeatInfo;
private long heartbeat;
private String charset;
public TcpDelegate() {
super();
}
/**
* 上传给tcp服务的初始数据一般是文件名文件路径等信息
*/
public TcpDelegate setParam(String params) {
if (TextUtils.isEmpty(params)) {
ALog.w(TAG, "tcp传输的数据不能为空");
return this;
}
this.params = params;
return this;
}
/**
* 设置心跳包传输的数据
*
* @param heartbeatInfo 心跳包数据
*/
public TcpDelegate setHeartbeatInfo(String heartbeatInfo) {
if (TextUtils.isEmpty(heartbeatInfo)) {
ALog.w(TAG, "心跳包传输的数据不能为空");
return this;
}
this.heartbeatInfo = heartbeatInfo;
return this;
}
/**
* 心跳间隔默认1s
*
* @param heartbeat 单位毫秒
*/
public TcpDelegate setHeartbeatInterval(long heartbeat) {
if (heartbeat <= 0) {
ALog.w(TAG, "心跳间隔不能小于1毫秒");
return this;
}
this.heartbeat = heartbeat;
return this;
}
/**
* 数据传输编码默认"utf-8"
*/
public TcpDelegate setCharset(String charset) {
if (!Charset.isSupported(charset)) {
ALog.w(TAG, "不支持的编码");
return this;
}
this.charset = charset;
return this;
}
}

@ -0,0 +1,77 @@
/*
* 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.tcp;
/**
* tcp任务配置
*
* @author aria
* Date: 2019-09-06
*/
public class TcpTaskConfig {
/**
* 上传给tcp服务的初始数据一般是文件名等信息
*/
private String params;
/**
* 心跳包传输的数据
*/
private String heartbeat;
/**
* 心跳间隔单位毫秒默认1s
*/
private long heartbeatInterval = 1000;
/**
* 数据传输编码默认"utf-8"
*/
private String charset = "utf-8";
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getHeartbeat() {
return heartbeat;
}
public void setHeartbeat(String heartbeat) {
this.heartbeat = heartbeat;
}
public long getHeartbeatInterval() {
return heartbeatInterval;
}
public void setHeartbeatInterval(long heartbeatInterval) {
this.heartbeatInterval = heartbeatInterval;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
}

@ -1,36 +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.inf;
/**
* Created by lyy on 2017/6/3.
*/
public abstract class AbsNormalTask<TASK_WRAPPER extends AbsTaskWrapper>
extends AbsTask<TASK_WRAPPER> {
/**
* 最高优先级命令最高优先级命令有以下属性
* 1在下载队列中有且只有一个最高优先级任务
* 2最高优先级任务会一直存在直到用户手动暂停或任务完成
* 3任务调度器不会暂停最高优先级任务
* 4用户手动暂停或任务完成后第二次重新执行该任务该命令将失效
* 5如果下载队列中已经满了则会停止队尾的任务
* 6把任务设置为最高优先级任务后将自动执行任务不需要重新调用start()启动任务
*/
public void setHighestPriority(boolean isHighestPriority) {
isHeighestTask = isHighestPriority;
}
}

@ -16,31 +16,119 @@
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; import android.app.Dialog;
import com.arialyy.aria.core.queue.DownloadTaskQueue; import android.util.Log;
import com.arialyy.aria.core.queue.UploadTaskQueue; import android.widget.PopupWindow;
import java.util.Iterator; import com.arialyy.aria.core.WidgetLiftManager;
import java.util.Map; import com.arialyy.aria.core.queue.DGroupTaskQueue;
import java.util.concurrent.ConcurrentHashMap; import com.arialyy.aria.core.queue.DTaskQueue;
import com.arialyy.aria.core.queue.UTaskQueue;
import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/** /**
* Created by AriaL on 2017/6/27. * Created by AriaL on 2017/6/27.
* 接收器 * 接收器
*/ */
public abstract class AbsReceiver implements IReceiver { public abstract class AbsReceiver implements IReceiver {
protected String TAG = getClass().getSimpleName();
/** /**
* 观察者对象map * 观察者对象
* key {@link #getKey(IReceiver)}指定
*/ */
public static final Map<String, Object> OBJ_MAP = new ConcurrentHashMap<>(); public Object obj;
/** /**
* 观察者对象类的完整名称 * 观察者对象类的完整名称
*/ */
public String targetName; private String targetName;
/** /**
* 当dialogdialogFragmentpopupwindow已经被用户使用了Dismiss事件或Cancel事件需要手动移除receiver * 当dialogdialogFragmentpopupwindow已经被用户使用了Dismiss事件或Cancel事件需要手动移除receiver
*/ */
public boolean needRmListener = false; private boolean needRmReceiver = false;
private boolean isFragment = false;
public boolean isLocalOrAnonymousClass = false;
public AbsReceiver(Object obj) {
this.obj = obj;
initParams();
}
private void initParams() {
try {
targetName = CommonUtil.getTargetName(obj);
Class clazz = obj.getClass();
if (CommonUtil.isLocalOrAnonymousClass(clazz)) {
isLocalOrAnonymousClass = true;
String parentName = CommonUtil.getTargetName(obj);
Class parentClazz = Class.forName(parentName);
handleFragmentOrDialogParam(parentClazz, true);
return;
}
handleFragmentOrDialogParam(clazz, false);
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleFragmentOrDialogParam(Class clazz, boolean isLocalOrAnonymousClass) {
final WidgetLiftManager widgetLiftManager = new WidgetLiftManager();
if (obj instanceof Dialog) {
needRmReceiver = widgetLiftManager.handleDialogLift((Dialog) obj);
return;
}
if (obj instanceof PopupWindow) {
needRmReceiver = widgetLiftManager.handlePopupWindowLift((PopupWindow) obj);
return;
}
if (CommonUtil.isFragment(clazz)){
isFragment = true;
}
if (CommonUtil.isDialogFragment(clazz)) {
isFragment = true;
if (isLocalOrAnonymousClass) {
Log.e(TAG, String.format(
"%s 是匿名内部类,无法获取到dialog对象,为了防止内存泄漏,请在dismiss方法中调用Aria.download(this).unRegister();来注销事件",
obj.getClass().getName()
));
return;
}
needRmReceiver = widgetLiftManager.handleDialogFragmentLift(getDialog(obj));
}
}
/**
* 获取DialogFragment的dialog
*
* @return 获取失败返回null
*/
private Dialog getDialog(Object obj) {
try {
Method method = obj.getClass().getMethod("getDialog");
return (Dialog) method.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
protected boolean isNeedRmListener() {
return needRmReceiver;
}
@Override public boolean isFragment() {
return isFragment;
}
/** /**
* 创建观察者对象map的key生成规则 * 创建观察者对象map的key生成规则
@ -69,14 +157,7 @@ public abstract class AbsReceiver implements IReceiver {
* 移除观察者对象 * 移除观察者对象
*/ */
private void removeObj() { private void removeObj() {
for (Iterator<Map.Entry<String, Object>> iter = OBJ_MAP.entrySet().iterator(); obj = null;
iter.hasNext(); ) {
Map.Entry<String, Object> entry = iter.next();
String key = entry.getKey();
if (key.equals(getKey())) {
iter.remove();
}
}
} }
@Override public void destroy() { @Override public void destroy() {
@ -85,7 +166,7 @@ public abstract class AbsReceiver implements IReceiver {
} }
/** /**
* 移除{@link DownloadTaskQueue}{@link DownloadGroupTaskQueue}{@link UploadTaskQueue}中注册的观察者 * 移除{@link DTaskQueue}{@link DGroupTaskQueue}{@link UTaskQueue}中注册的观察者
*/ */
protected abstract void unRegisterListener(); protected abstract void unRegisterListener();
} }

@ -16,10 +16,10 @@
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.CheckResult; import com.arialyy.aria.core.common.AbsEntity;
import com.arialyy.aria.core.common.Suggest;
import com.arialyy.aria.core.common.controller.NormalController;
import com.arialyy.aria.core.common.controller.BuilderController; import com.arialyy.aria.core.common.controller.BuilderController;
import com.arialyy.aria.core.common.controller.NormalController;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
@ -31,7 +31,6 @@ public abstract class AbsTarget<TARGET extends AbsTarget> {
protected String TAG; protected String TAG;
private AbsEntity mEntity; private AbsEntity mEntity;
private AbsTaskWrapper mTaskWrapper; private AbsTaskWrapper mTaskWrapper;
private String mTargetName;
protected AbsTarget() { protected AbsTarget() {
TAG = CommonUtil.getClassName(this); TAG = CommonUtil.getClassName(this);
@ -46,14 +45,6 @@ public abstract class AbsTarget<TARGET extends AbsTarget> {
return mEntity; return mEntity;
} }
public String getTargetName() {
return mTargetName;
}
public void setTargetName(String mTargetName) {
this.mTargetName = mTargetName;
}
/** /**
* 获取任务实体 * 获取任务实体
*/ */
@ -69,7 +60,6 @@ public abstract class AbsTarget<TARGET extends AbsTarget> {
* *
* @param str 扩展数据 * @param str 扩展数据
*/ */
@CheckResult(suggest = Suggest.TASK_CONTROLLER)
public TARGET setExtendField(String str) { public TARGET setExtendField(String str) {
if (TextUtils.isEmpty(str)) return (TARGET) this; if (TextUtils.isEmpty(str)) return (TARGET) this;
if (TextUtils.isEmpty(mEntity.getStr()) || !mEntity.getStr().equals(str)) { if (TextUtils.isEmpty(mEntity.getStr()) || !mEntity.getStr().equals(str)) {
@ -84,10 +74,10 @@ public abstract class AbsTarget<TARGET extends AbsTarget> {
/** /**
* 重置状态将任务状态设置为未开始状态 * 重置状态将任务状态设置为未开始状态
* 注意如果在后续方法调用链中没有调用 {@link NormalController#stop()}{@link NormalController#cancel()} * 注意如果在后续方法调用链中没有调用 {@link NormalController#stop()}{@link NormalController#cancel()}
* {@link NormalController#resume()}{@link BuilderController#create()}{@link BuilderController#add()} * {@link NormalController#resume()}{@link BuilderController#create()}{@link
* BuilderController#add()}
* 等操作任务的方法那么你需要调用{@link NormalController#save()}才能将修改保存到数据库 * 等操作任务的方法那么你需要调用{@link NormalController#save()}才能将修改保存到数据库
*/ */
@CheckResult(suggest = "after use #create()、#stop()、#cancel()、#resume()、#save()?")
public TARGET resetState() { public TARGET resetState() {
getTaskWrapper().getEntity().setState(IEntity.STATE_WAIT); getTaskWrapper().getEntity().setState(IEntity.STATE_WAIT);
getTaskWrapper().setRefreshInfo(true); getTaskWrapper().setRefreshInfo(true);

@ -15,6 +15,8 @@
*/ */
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import com.arialyy.aria.core.common.AbsEntity;
/** /**
* Created by lyy on 2019/4/5. * Created by lyy on 2019/4/5.
* 普通任务配置处理 * 普通任务配置处理

@ -49,5 +49,11 @@ public interface IReceiver {
* *
* @return {@link ReceiverType} * @return {@link ReceiverType}
*/ */
@ReceiverType String getType(); ReceiverType getType();
/**
* 判断是否是fragment如果是fragment在activity销毁时需要将其从receiver中移除
*/
boolean isFragment();
} }

@ -15,19 +15,18 @@
*/ */
package com.arialyy.aria.core.inf; package com.arialyy.aria.core.inf;
import androidx.annotation.StringDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* {@link AbsReceiver}类型 * {@link AbsReceiver}类型
*/ */
@StringDef({ public enum ReceiverType {
ReceiverType.DOWNLOAD, DOWNLOAD(1, "download"),
ReceiverType.UPLOAD, UPLOAD(2, "upload");
})
@Retention(RetentionPolicy.SOURCE) String name;
public @interface ReceiverType { int type;
String DOWNLOAD = "download";
String UPLOAD = "upload"; ReceiverType(int type, String name) {
this.type = type;
this.name = name;
}
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save